diff --git a/.gitignore b/.gitignore index 5e59b862ba4..b53a8496960 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # Gradle build files /.gradle/ /build/ -src/main/resources/docs/ # IDEA files /.idea/ diff --git a/.travis.yml b/.travis.yml index 924a42eb8da..b1734874d72 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,8 @@ matrix: - jdk: openjdk11 script: >- - ./config/travis/run-checks.sh && - ./gradlew clean checkstyleMain checkstyleTest test coverage coveralls asciidoctor + ./config/travis/run-checks.sh && + ./gradlew clean checkstyleMain checkstyleTest test coverage coveralls asciidoctor deploy: skip_cleanup: true diff --git a/README.adoc b/README.adoc index e36efe534bb..3bb13f06bf8 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,9 @@ -= Address Book (Level 3) += FlashSpeed ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level3[image:https://travis-ci.org/se-edu/addressbook-level3.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level3[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level3?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level3/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level3?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level3&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] - +https://travis-ci.org/AY1920S2-CS2103T-W17-1/main[image:https://travis-ci.org/AY1920S2-CS2103T-W17-1/main.svg?branch=master[Build Status]] +//https://ci.appveyor.com/project/damithc/addressbook-level3[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] +//https://coveralls.io/github/se-edu/addressbook-level3?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level3/badge.svg?branch=master[Coverage Status]] ifdef::env-github[] image::docs/images/Ui.png[width="600"] @@ -14,22 +12,49 @@ endif::[] ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] +  + +ifdef::env-github[] +image::docs/images/Ui2.png[width="600"] +endif::[] + +ifndef::env-github[] +image::images/Ui2.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 is it? + +FlashSpeed is a text-based flashcard application specifically designed for university students who are learning a foreign language. University students often have hectic schedules. With this in mind, FlashSpeed was created to allow students to be able to study and revise foreign vocabulary on the fly. With a single command, you can start a quick study session on FlashSpeed whenever! + +Keeping, flipping, and tracking physical flashcards can be a pain. FlashSpeed enhances the studying process by having virtual flashcards and a smarter review system. Users will be tested more frequently on flashcards that they had trouble memorizing previously. + +By using FlashSpeed, you will learn faster and remember for longer! + +== Who is it for? + +FlashSpeed is intended for university students reading a foreign language module who need an effective way to memorize a lot of new vocabulary fast and for the long term. It can also be used by students who need to remember a lot of information (e.g. historical dates, definitions, etc.) before an exam. + +Of course, anyone who needs a powerful memorization tool can benefit from using FlashSpeed! == Site Map -* <> -* <> -* <> -* <> -* <> +* https://ay1920s2-cs2103t-w17-1.github.io/main/UserGuide.html[User Guide] +* https://ay1920s2-cs2103t-w17-1.github.io/main/DeveloperGuide.html[Developer Guide] +* https://ay1920s2-cs2103t-w17-1.github.io/main/AboutUs.html[About Us] +* https://ay1920s2-cs2103t-w17-1.github.io/main/ContactUs.html[Contact Us] + +//* <> +//* <> +//* <> +//* <> +//* <> == Acknowledgements -* Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by +* FlashSpeed is built on top of the AddressBook-Level3 project created by SE-EDU initiative at https://se-education.org. +* Some parts of the sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by _Marco Jakob_. * Libraries used: https://openjfx.io/[JavaFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/junit-team/junit5[JUnit5] diff --git a/build.gradle b/build.gradle index 93029ef8262..73f18d164e7 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ plugins { } // Specifies the entry point of the application -mainClassName = 'seedu.address.Main' +mainClassName = 'com.flashspeed.Main' sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 @@ -57,6 +57,12 @@ dependencies { implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-web', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-web', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-web', version: javaFxVersion, classifier: 'linux' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' @@ -67,7 +73,8 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + // archiveName = 'addressbook.jar' + archiveName = 'flashspeed.jar' destinationDir = file("${buildDir}/jar/") } @@ -120,22 +127,24 @@ asciidoctor { outputDir "${buildDir}/docs" options = [ - template_dirs: [file("${sourceDir}/templates")], + template_dirs: [file("${sourceDir}/templates")], ] attributes = [ - linkcss: true, - stylesheet: 'gh-pages.css', - 'source-highlighter': 'coderay', - icons: 'font', - experimental: true, - sectlinks: true, - idprefix: '', // for compatibility with GitHub preview - idseparator: '-', - 'site-root': "${sourceDir}", // must be the same as sourceDir, do not modify - 'site-name': 'AddressBook-Level3', - 'site-githuburl': 'https://github.com/se-edu/addressbook-level3', - 'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) + linkcss: true, + stylesheet: 'gh-pages.css', + 'source-highlighter': 'coderay', + icons: 'font', + experimental: true, + sectlinks: true, + idprefix: '', // for compatibility with GitHub preview + idseparator: '-', + 'site-root': "${sourceDir}", // must be the same as sourceDir, do not modify + // 'site-name': 'AddressBook-Level3', + 'site-name': 'FlashSpeed', + // 'site-githuburl': 'https://github.com/se-edu/addressbook-level3', + 'site-githuburl': 'https://github.com/AY1920S2-CS2103T-W17-1/main', + 'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) ] options['template_dirs'].each { diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index 458e6134f45..d2826182211 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,53 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 3 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + +FlashSpeed was developed by the https://github.com/AY1920S2-CS2103T-W17-1[CS2103T-W17-1] 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]] [<>] +=== AMIRUL ARDY BIN MOHAMED RASI +image::amrl.png[width="150", align="left"] +{empty}[https://github.com/amrl[github]] [<>] -Role: Project Advisor +Role: Technical Lead + +Responsibilities: User Guide, Deliverables and deadline, Integration, Automated build and test tools ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== CHIEW KOK SENG +image::kschiew.png[width="150", align="left"] +{empty}[http://github.com/kschiew[github]] [<>] -Role: Team Lead + -Responsibilities: UI +Role: Software Architect + +Responsibilities: App Architecture, Logic, Storage, Statistics ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== LEONG ZHAN HAO, NICHOLAS +image::ncslzh.png[width="150", align="left"] +{empty}[https://github.com/ncslzh[github]] [<>] -Role: Developer + -Responsibilities: Data +Role: DevOps + +Responsibilities: Developer Guide, Agenda/Issue tracker, Game Algorithm ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== LIAO LIXIN +image::olixino.png[width="150", align="left"] +{empty}[http://github.com/oLiXino[github]] [<>] Role: Developer + -Responsibilities: Dev Ops + Threading +Responsibilities: Play View, Model, Game Component ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== PHAM TRAN TUAN LINH +image::lacedaemon98.png[width="150", align="left"] +{empty}[http://github.com/lacedaemon98[github]] [<>] -Role: Developer + -Responsibilities: UI +Role: Developer, Designer + +Responsibilities: UI/UX, Visual, Logic ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 81be279ef6d..4a9fbc60747 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -2,6 +2,14 @@ :site-section: ContactUs :stylesDir: stylesheets -* *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level3/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. +* *Bug reports, Suggestions* : Post in our https://github.com/AY1920S2-CS2103T-W17-1/main/issues[issue tracker] if you noticed bugs or have suggestions on how to make improvement. * *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] -* *Email us* : You can also reach us at `damith [at] comp.nus.edu.sg` +* *Email us* : You can also reach us at +** `e0310544 [at] u.nus.edu` +** `e0309862 [at] u.nus.edu` +** `e0332767 [at] u.nus.edu` +** `pham98 [at] u.nus.edu` +** `lixin11 [at] u.nus.edu` + + + diff --git a/docs/DevOps.adoc b/docs/DevOps.adoc index 2aa5a6bc0c1..a0868237189 100644 --- a/docs/DevOps.adoc +++ b/docs/DevOps.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Dev Ops += FlashSpeed - Dev Ops :site-section: DeveloperGuide :toc: :toc-title: @@ -12,7 +12,7 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S2-CS2103T-W17-1/main == Build Automation diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 3d65905a853..96283194b1c 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Developer Guide += FlashSpeed - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -12,9 +12,45 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S2-CS2103T-W17-1/main/tree/master -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `FlashSpeed Foundation`      Since: `Feb 2020`      Licence: `MIT` + +// tag::introduction[] +== Introduction +FlashSpeed is a text-based flashcard application specifically designed for university students who are learning a foreign language. + +FlashSpeed enhances the revision process by implementing a <> in its design. Users will be tested more frequently on words that they have gotten wrong in a game session. + +The main features of FlashSpeed allows users to: + +* create their own <> of <> (flashcards) +* choose a deck to <> (review) which uses the smart review design mentioned above. + +[[Purpose]] +=== Purpose + +This document specifies architecture, software design decisions and implementation for the FlashSpeed application. + +=== Audience +This document is intended for anyone who wants to understand the system architecture, design and implementation of FlashSpeed. + +The following groups are in particular are the intended audience of this document. + +* FlashSpeed developers +* FlashSpeed features enhancement team members + +=== How to use + +* Section 3 - Design contains information about the main components of FlashSpeed. +** Firstly, refer to <<#Design-Architecture, 3.1 Architecture>> section to learn more about the overall architecture. +** Then proceed to sections 3.2 - 3.6 to learn more about each individual component in the architecture. + +* Section 4 - Implementation contains information about the main commands and the implementations of each command used in FlashSpeed. + +* Appendix - Information related to the development process and design choices of FlashSpeed. + +// end::introduction[] == Setting up @@ -22,34 +58,47 @@ Refer to the guide <>. == Design +// tag::design-arch[] + [[Design-Architecture]] === Architecture +FlashSpeed is mainly built on top of a few core components, runner components, and helper components. The core is where +the bulk of the design decisions are made and data processing is performed. + +The *_Architecture Diagram_* below gives a high-level overview of the FlashSpeed App. + .Architecture Diagram image::ArchitectureDiagram.png[] -The *_Architecture Diagram_* given above explains the high-level design of the App. Given below is a quick overview of each component. +Given below is a quick overview of each component. -[TIP] -The `.puml` files used to create diagrams in this document can be found in the link:{repoURL}/docs/diagrams/[diagrams] folder. -Refer to the <> to learn how to create and edit diagrams. +*The Runners:* + -`Main` has two classes called link:{repoURL}/src/main/java/seedu/address/Main.java[`Main`] and link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp`]. It is responsible for, +`Main` has two classes called link:{repoURL}/src/main/java/com/flashspeed/Main.java[`Main`] and link:{repoURL}/src/main/java/com/flashspeed/MainApp.java[`MainApp`]. It is responsible for, -* At app launch: Initializes the components in the correct sequence, and connects them up with each other. -* At shut down: Shuts down the components and invokes cleanup method where necessary. +* At launch: Initializes the core components in the correct sequence, sets up their initial states, and connects them up +with each other. +* At exit: Performs cleanup of components where necessary and shuts down FlashSpeed. + +*The Helpers:* + <> represents a collection of classes used by multiple other components. -The following class plays an important role at the architecture level: +The following classes plays an important role at the architecture level: -* `LogsCenter` : Used by many classes to write log messages to the App's log file. +* `LogsCenter` : Writes log messages from many different running components to the FlashSpeed's log file and console. +* `Index` : Provides storage of index numbers and easy conversion between one- and zero-based indices. +* `Messages` : Keeps many user-visible messages for invalid user actions. +* `Exceptions` : Contains different types of specific exceptions which can occur due to invalid user actions. -The rest of the App consists of four components. +*The Core:* + -* <>: The UI of the App. +* <>: The UI of FlashSpeed. * <>: The command executor. -* <>: Holds the data of the App in-memory. -* <>: Reads data from, and writes data to, the hard disk. +* <>: Holds the data and current state of FlashSpeed in-memory. +* <>: Reads data from and writes data to a data file on the hard disk. + +// end::design-arch[] Each of the four components @@ -61,33 +110,47 @@ For example, the `Logic` component (see the class diagram given below) defines i .Class Diagram of the Logic Component image::LogicClassDiagram.png[] +{empty} + + [discrete] -==== How the architecture components interact with each other +==== 3.1.1 How the core components interact with each other -The _Sequence Diagram_ below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The _Sequence Diagram_ below shows how the core components interact with each other for the scenario where the user issues the command `remove 2`. -.Component interactions for `delete 1` command +.Component interactions for the `remove 2` command image::ArchitectureSequenceDiagram.png[] The sections below give more details of each component. +{empty} + + +//tag::UI-component[] [[Design-Ui]] === UI component .Structure of the UI Component image::UiClassDiagram.png[] -*API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] +*API* : link:{repoURL}/src/main/java/com/flashspeed/ui/Ui.java[`Ui.java`] -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `DeckListPanel`, `CardListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. -The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] +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/com/flashspeed/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] The `UI` component, * Executes user commands using the `Logic` component. * Listens for changes to `Model` data so that the UI can be updated with the modified data. +* `HelpWindow` will only be shown when executing `help` command. +* `StatisticsPopUp` will only be shown after finishing or stopping a *Play session*. +* Either `CardListPanel` or `PlayPanel` is displayed depending on the current <>. + +//end::UI-component[] + + +{empty} + +// tag::design-logic[] [[Design-Logic]] === Logic component @@ -96,151 +159,426 @@ The `UI` component, image::LogicClassDiagram.png[] *API* : -link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] +link:{repoURL}/src/main/java/com/flashspeed/logic/Logic.java[`Logic.java`] -. `Logic` uses the `AddressBookParser` class to parse the user command. +. `Logic` uses the `MasterParser` class to parse the user command. . This results in a `Command` object which is executed by the `LogicManager`. -. The command execution can affect the `Model` (e.g. adding a person). +. The command execution can affect the `Model` (e.g. adding a card). . The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. . In addition, the `CommandResult` object can also instruct the `Ui` to perform certain actions, such as displaying help to the user. Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. - -.Interactions Inside the Logic Component for the `delete 1` Command +// end::design-logic[] +.Interactions inside the Logic Component for the `delete 1` command image::DeleteSequenceDiagram.png[] -NOTE: The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +NOTE: The lifeline for `RemoveDeckCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. + + +{empty} + [[Design-Model]] +// tag::model[] === Model component .Structure of the Model Component image::ModelClassDiagram.png[] -*API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] +{empty} + + +.Structure of the Deck Component within the Model Component +image::ModelClassDeckDiagram.png[] + +*API* : link:{repoURL}/src/main/java/com/flashspeed/model/Model.java[`Model.java`] The `Model`, * stores a `UserPref` object that represents the user's preferences. -* stores the Address Book data. -* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the Library's current state and data. +* stores and manipulates a `GameManager` object that represents one game session. +* stores and manipulates a `Deck` object that represents the deck that the user is viewing when user is in <>. +* stores and manipulates a `Card` object that represents the card that the user is playing with when user is in <>. +* stores and manipulates `View` object that represents the <> that the user is currently in. +* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * does not depend on any of the other three components. -[NOTE] -As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` can reference. This would allow `Address Book` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object. An example of how such a model may look like is given below. + - + -image:BetterModelClassDiagram.png[] +{empty} + +// end::model[] + [[Design-Storage]] +// tag::storage[] === Storage component .Structure of the Storage Component image::StorageClassDiagram.png[] -*API* : link:{repoURL}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] + +*API* : link:{repoURL}/src/main/java/com/flashspeed/storage/Storage.java[`Storage.java`] The `Storage` component, -* can save `UserPref` objects in json format and read it back. -* can save the Address Book data in json format and read it back. +* can save `UserPref` objects in JSON format and read it back. +* can save all the decks and cards created in JSON format and read them back. + + +// end::storage[] +{empty} + [[Design-Commons]] === Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `com.flashspeed.commons` package. + +{empty} + + +=== Logging + +We are using `java.util.logging` package for logging. The `LogsCenter` class is used to manage the logging levels and logging destinations. + +* The logging level can be controlled using the `logLevel` setting in the configuration file (See <>) +* The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level +* Currently log messages are output through: `Console` and to a `.log` file. + +*Logging Levels* + +* `SEVERE` : Critical problem detected which may possibly cause the termination of the application +* `WARNING` : Can continue, but with caution +* `INFO` : Information showing the noteworthy actions by the App +* `FINE` : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size + +{empty} + + +[[Implementation-Configuration]] +=== Configuration + +Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: `config.json`). + == Implementation This section describes some noteworthy details on how certain features are implemented. -// tag::undoredo[] -=== [Proposed] Undo/Redo feature -==== Proposed Implementation +{empty} + -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: +// tag::createdeck[] -* `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. +=== Creating a Deck +==== Current Implementation -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +The `create` command allows user to create a new Deck in the current Library. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +Accepted syntax: `create DECK_NAME` -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. +This functionality is implemented by getting the Deck based on the index provided. Subsequently, the Card(s) that belongs to the selected Deck will be displayed on `CardListPanel` via a `TableView`. -image::UndoRedoState0.png[] +===== Validation and extraction of input in parser +The validation of the arguments in the `create` command is performed in `CreateDeckCommandParser#parse()`. It ensures +that the user has entered a non-null deck name. -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. +In `CreateDeckCommandParser#parse()`, the `DECK_NAME` of is extracted +from the arguments in the `create` command. The `DECK_NAME` is converted to a Name object. An `CreateDeckCommand` +object is then constructed with the Deck name as its parameter. -image::UndoRedoState1.png[] +===== Execution of Command object +When `CreateDeckCommand#execute()` is executed, an empty Deck with the Name parsed in the `CreateDeckCommand` will be +created when the Model Manager invokes the `ModelManager#selectDeck()` command. +After that, `ModelManager#setSelectedDeck()` method will be called to update the UI and display the Deck content +on `CardListPanel`. Lastly, the name of the selected Deck will be displayed together with the +`MESSAGE_SUCCESS` on the `ResultDisplay` panel. -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`. +The following sequence diagram shows the sequence of operations due to a `create` command. -image::UndoRedoState2.png[] +.Interactions inside the Model and Logic components when the user enters `create Japanese` into the input box. +image::CreateSequenceDiagram.png[Something] -[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`. +// tag::create-deck-consideration[] +==== Design Considerations +===== Aspect: If the user is already viewing another deck and decides to create a new deck, there was a consideration whether to switch the UI for the user view to the new deck or continue to let the user view the current deck. -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. +* **Alternative 1 (current choice):** Switch the view to the new Deck +** Pros: Able to use the new Deck immediately without typing an additional command to select it. +* **Alternative 2:** Keep the view at the current Deck +** Pros: Don't have to type in an additional command to return back to the current Deck if a new Deck is created -image::UndoRedoState3.png[] +We chose Alternative 1 in the end as we believed that it will be more likely for the user to want to use the new deck immediately after creating it. +// end::create-deck-consideration[] -[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. +[[Four-Two-Two]] +===== Aspect: Naming convention of command key words. +Initially, both `CreateDeckCommand` and `AddCardCommand` share the same keyword, which is the `add` keyword. +In order to distinguish these two commands from each other, +the Model Manager will check if any deck is currently selected. +If there is, `AddCardCommandParser#parse()` will be called to parse the arguments. +Otherwise, `CreateDeckCommandParser#parse()` will be called. + +The benefit of this design is that it results in fewer number of command words. +This helps the user on the navigability of the application due to a few number of command words to remember. + +However, the glaring disadvantage is that unexpected outcomes are more likely to occur. +For example, assume that the user wants to create a new deck. So, he/she types in the following command: -The following sequence diagram shows how the undo operation works: +`create Deck 2` -image::UndoSequenceDiagram.png[] +However, the user has forgotten that a deck is currently being selected. +Therefore, the `AddCardCommandParser#parse()` will be invoked. +This is certainly not the expected outcome that the user has expected. -NOTE: The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +And so, our team has decided to implement the current approach, +which is to assign different keywords to these two different feature. +// end::createdeck[] +{empty} + -The `redo` command does the opposite -- it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +//tag::selectdeck[] +=== Selecting a Deck [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. +UI implementation will be discussed in detail only in this section. In other sections, detailed UI implementation will be omitted. + +==== Current Implementation + +The `select` command allows user to view the Card content of a Deck. -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. +Accepted syntax: `select INDEX` -image::UndoRedoState4.png[] +This functionality is implemented by getting the Deck based on the index provided. Subsequently, the selected Deck will be highlighted on `DeckListPanel` and the Card(s) that belongs to it will be displayed on `CardListPanel` via a `TableView`. -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. We designed it this way because it no longer makes sense to redo the `add n/David ...` command. This is the behavior that most modern desktop applications follow. +The validation of the arguments in the `select` command is performed in `SelectDeckCommandParser#parse()`. It ensures that the user has entered a valid index (valid data type and range). This is also used for separation of parsing +logic and model management logic. -image::UndoRedoState5.png[] +In `SelectDeckCommandParser#parse()`, the `INDEX` of the selected Deck is extracted +from the arguments in the `select` command. The `INDEX` is converted to an Index object. An `SelectCardCommand` +object is then constructed with the Index as its parameter. -The following activity diagram summarizes what happens when a user executes a new command: +When `SelectDeckCommand#execute()` is executed a list of currently available Deck is requested from the `ModelManager#getFilteredDeckList()` method. The `ModelManager#selectDeck()` command will be invoked to update the variable that keeps track of the current Deck. After that, `ModelManager#setSelectedDeck()` method will be called to update the UI by highlighting the selected Deck on `DeckListPanel` and displaying its content on `CardListPanel`. Lastly, the name of the selected Deck will be displayed together with the `MESSAGE_SUCCESS` on the `ResultDisplay` panel. -image::CommitActivityDiagram.png[] +==== Design Considerations +The UI will have to be constantly updated when we select to view a Deck, and other Decks might be selected afterward. As a result, an `ObservableValue` variable will have to be updated constantly via the `ModelManager#setSelectedDeck()` method. Various event listeners are implemented in the UI classes (e.g `CardListPanel`, `DeckListPanel`) in order to instantly react if there is any changes to the selected Deck. + +.Interactions inside Model, Logic and UI components to reflect UI changes when selecting a deck +image::SelectDeckSequenceDiagram.png[] + +//end::selectdeck[] +{empty} + + + +// tag::addcard[] + +=== Adding a Card +==== Current Implementation + +The `add` command allows user to create a new Card in the current Deck. + +Accepted syntax: `add FRONT_VALUE:BACK_VALUE` + +This functionality is implemented by getting the Deck based on the current deck selected. +The Model Manager will be responsible of keeping track of the current deck. +Subsequently, the Model Manager creates a new card adds it to the current Deck. +The display on `CardListPanel` will be updated via updating the `TableView`. + +===== Validation and extraction of input in parser +The validation of the arguments in the `add` command is performed in `AddCardCommandParser#parse()`. It ensures +that the user has entered a non-null front value as well as a non-null back value. +The lack thereof will cause a `InvalidFaceValueException` to be thrown. + +In `AddCardCommandParser#parse()`, the `FRONT_VALUE` and the `BACK_VALUE` are extracted +from the arguments in the `add` command. +Both values will be converted to a `FrontFace` object and a `BackFace` object respectively. +A `AddCardCommand` object is then constructed with the 'FrontFace' and 'BackFace' objects as its parameters. + +===== Execution of Command object +When `AddCardCommand#execute()` is called, a `Card` object with the `FrontFace` and `BackFace` +parsed in the `CreateDeckCommand` will be +created when the Model Manager invokes the `ModelManager#addCard()` command. +After that, `ModelManager#setSelectedDeck()` method will be called to update the UI and display the Deck content +on `CardListPanel`. Lastly, the name of the selected Deck will be displayed together with the +`MESSAGE_SUCCESS` on the `ResultDisplay` panel. + +The following sequence diagram shows the sequence of operations due to an `add` command. + +.Interactions inside Model and Logic components when user enters `add ありがとう:thanks` into the input box. +image::AddSequenceDiagram.png[] ==== Design Considerations +See <>. +// end::addcard[] + +{empty} + + +// tag::editcard[] + +=== Editing a Card: Shortcut Formats +==== Current Implementation + +The `edit` command allows for the values of a Card's face(s) to be changed. + +Accepted syntax: + +* `edit INDEX FRONT:BACK` +* `edit INDEX :BACK` +* `edit INDEX FRONT:` + +This functionality is implemented by replacing the Card to be edited in the Deck with a new Card containing +the new face values (`FRONT` and `BACK`). The shortcut versions of the command (second and third formats above) allows +for one face value of the Card to be edited while preserving the other face value. In this situation, the unedited face +value in the new Card will be a blank string (since either `FRONT` or `BACK` will be a blank string). Subsequently, this +blank value will be replaced by the associated value in the Card to be replaced. -===== Aspect: How undo & redo executes +Below is a summary of the operations flow during the editing of a card. -* **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. +.Operations flow during an Edit command. +image::EditCardActivityDiagram.png[] -===== Aspect: Data structure to support the undo/redo commands +===== Validation and extraction of input in parser +The validation of the arguments in the `edit` command is performed in `EditCommandParser#parse()`. Note that +the validation only checks that the command is well-formed, i.e. understandable by FlashSpeed. It does not check for the validity of the command +in the current environment (e.g. if we are currently in Deck view or not). This is for separation of parsing +logic and model management logic. -* **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[] +In `EditCommandParser#parse()`, the `INDEX` of the card to be edited and the new face value(s) are extracted +from the arguments in the `edit` command. The `INDEX` is converted to an Index object. An `EditCardCommand` +object is then constructed with the Index and the new Card as its parameters. -// tag::dataencryption[] -=== [Proposed] Data Encryption +===== Execution of Command object +When `EditCardCommand#execute()` is executed, the environment is then checked. The `edit` command is only valid when +we are currently in a Deck, thus a check on the current view is performed using `ModelManager#getView()`. Then +the Index of the card to be edited is checked by ensuring it is in the range of [0, size of current Deck) using +`ModelManager#getCurrentDeck().getSize()`. -_{Explain here how the data encryption feature will be implemented}_ +To perform a replacement of a Card in the current Deck, we need the old Card and the new Card. The old Card is required +so we can know which Card is to be replaced via an equality check and also to know the face value which needs to be +preserved (if needed). We can get the Card to be edited using `ModelManager#getCard()` with the provided Index. +The new Card can then be created. +We can then perform the replacement using `ModelManager#replaceCard()` with the old Card and the new Card as the parameters. -// end::dataencryption[] +The following sequence diagram shows the sequence of operations due to an `edit` command. + +.Operations performed for the `edit 1 fr:bk` command. +image::EditSequenceDiagram.png[] + +==== Design Considerations + +===== Aspect: How the replacement Card is formed during the start of execution + +* **Alternative 1 (current choice):** A blank string in a face of the new Card means we use the face value in the Card to be replaced. +** Pros: Easy to implement. Can use the extracted values in the arguments as is. Let the final step (`UniqueCardList#replace()`) handle the replacement logic. +** Cons: From `EditCardCommandParser` until the end of the command execution in `LogicManager`, there may exist a Card with a face containing a blank string. May not be a desirable object to have. +* **Alternative 2:** Get the Card to be edited directly in `EditCardCommandParser` so we can immediately produce the new Card with its final face values. +** Pros: The Card to replace the old Card will be fully formed from the start. +** Cons: No separation of parsing and model management logic since we would need to do a view check and get a Card from the current Deck all while in the parser. + +// end::editcard[] + +{empty} + + +// tag::play[] +=== Starting a play session +==== Current Implementation + +The `play` command creates a new session to play with a specific deck. + +Accepted syntax: `play INDEX` + +The play command changes the mode of the application to `PLAY` mode and creates a new session with the Deck at the given `INDEX`. +The value of the `FRONT` of the selected `Deck` will be displayed to the user. + +===== Validation and extraction of input in parser +The first validation of the `play` command is performed in `PlayCommandParser#parse()`. +The validation only checks that the `play` command has the correct format as the `INDEX` argument is given by the user and it is performed on the login level. + +In `PlayCommandParser#parse()`, the `INDEX` of the deck is extracted from the arguments in the `play` command. The `INDEX` is converted to an Index object. An `PlayCommand` +object is then constructed with the Index. + + +===== Execution of Command object +After the object of the `PlayCommand' is constructed, `PlayCommand#execute()` will be executed and the second validation of the `play` command is performed. +This validation firstly checks if the given `INDEX` argument is a non-negative integer and is within the number of cards in the selected Deck. Then the validation checks if +there is any card currently in the selected deck by checking if the `FRONT` face and `BACK` face of the card returned by `ModelManager#play()` are both empty. + +A valid `play` command will change the `MODE` of the `ModelManager` to `PLAY` mode and a `GameManager` object will be constructed in `ModelManager`. +The first card of the selected deck is obtained using `deck#asUnmodifiableObservableList().get(0)` and returned to UI. The `FRONT` face of the first card will be displayed to the user. + +The following sequence diagram shows how the `play` operation works. + +.Interactions inside Logic and Model components when `play 1` is executed +image::PlaySequenceDiagram.png[] + +{empty} + + +// tag::flip[] +=== Flipping a Card +==== Current Implementation + +The `flip` command flips a card in the selected deck to view the `BACK` face of the card. + +Accepted syntax: `flip` + +The `flip` command displays the `BACK` face of the card that the user is currently playing with to the user so that user is able to check if his or her answer is correct. + +===== Validation and extraction of input in parser +No user parameter is required, hence a parser is not needed. + +===== Execution of Command object +An `FlipCommand` object is constructed and `FlipCommand#execute()` is executed. In `FlipCommand#execute()`, validation for the `flip` command is performed. +The validation will check if `ModelManager` is in `PLAY` mode using `ModelManager#getMode()`. if `ModelManager` is in `PLAY` mode, then the validation will check if the card has been flipped +by checking if the returned `BACK` face of the card is empty since a card can only be flipped once. + +After that, `ModelManager#flip()` will be executed. In `ModelManager#flip()`, `GameManager#flip()` will be executed and the `BACK` face of the card is obtained using `GameManager#cards.get(counter).getBackFace()` and returned to `ModelManager`. + +A valid `flip` command returns the `BACK` face of the card that the user is currently playing to the UI and displays it to the user. + +The following sequence diagram shows how the `flip` operation works. + +.Interactions inside Logic and Model components when `flip` is executed +image::FlipSequenceDiagram.png[] + +{empty} + + +// tag::answer[] +=== Answering in a play session +==== Current Implementation + +User answers to the card that he or she is currently playing with using `yes` or `no` command. + +Accepted syntax: `yes` or `no` + +After flipping the card, users indicates if he or she gets the correct answer by using `yes` and `no` command. + +===== Validation and extraction of input in parser +No user parameter is required, hence a parser is not needed. + + +===== Execution of Command object +An `AnswerYesCommand` or `AnswerNoCommand` object is constructed and `AnswerYesCommand#execute()` or `AnswerNoCommand#execute()` is executed accordingly. Validation for the `yes` and `no` command is performed to check +if if `ModelManager` is in `PLAY` mode using `ModelManager#getMode()`. if `ModelManager` is in `PLAY` mode, then the validation will check if the card has been flipped +using `ModelManager#getGame().isFlipped()` since a card should not have been flipped before user answers to the card. + +After that, `ModelManager#answerYes()` or `ModelManager#answerNo()` will be executed accordingly. In `ModelManager#answerYes()` and `ModelManager#answerNo()`, `GameManager#answerYes()` and `GameManager#answerNo()` will be executed accordingly +and the next card is obtained using `GameManager#cards.get(counter)` and returned to `ModelManager`. `ModelManager` will check if `ModelManager` will check if the session has ended as the user have run through every card in the deck by checking +if the returned card is empty. + +A valid `yes` or `no` command returns the next card to the UI and the `FRONT` face of the card is displayed to the user. + +The following sequence diagrams show how the `yes` and `no` operation work. + +.Interactions inside Logic and Model components when `yes` is executed +image::AnswerYesSequenceDiagram.png[] +.Interactions inside Logic and Model components when `no` is executed +image::AnswerNoSequenceDiagram.png[] + +// end::play[] + +// tag::design-consideration-play[] +==== Design Considerations +===== Aspect: Using `yes` and `no` instead of the actual answer. +* **Alternative 1 (current choice):** Using a simple yes or no +** Pros: User can definitively choose if their answer was correct or not. This leads to accurate evaluation and statistics calculation. +** Cons: Not as interactive as if the user were to type in the correct word/sentence itself. +* **Alternative 2:** Typing in the actual answer itself. +** Pros: More interactive to the user. +** Cons: Typos or slightly incomplete (but correct) answers can be typed it by the user. +As the answers typed in mush exactly match the one on the card, it may result in inaccurate evaluation and statistics calculation at the end of the game. +// end::design-consideration-play[] === Logging @@ -257,11 +595,6 @@ We are using `java.util.logging` package for logging. The `LogsCenter` class is * `INFO` : Information showing the noteworthy actions by the App * `FINE` : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size -[[Implementation-Configuration]] -=== Configuration - -Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: `config.json`). - == Documentation Refer to the guide <>. @@ -274,19 +607,25 @@ Refer to the guide <>. Refer to the guide <>. +// tag::appendixA[] + [appendix] == Product Scope *Target user profile*: -* has a need to manage a significant number of contacts +* has a need to memorize a large number of new vocabulary words in a foreign language * prefer desktop apps over other types * can type fast * prefers typing over mouse input * is reasonably comfortable using CLI apps +* can accomplish most tasks faster via CLI, compared to a hypothetical GUI-version + +*Value proposition*: study new vocabulary words anytime and anywhere -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +// end::appendixA[] +// tag::appendixB[] [appendix] == User Stories @@ -297,86 +636,438 @@ Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (un |Priority |As a ... |I want to ... |So that I can... |`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App -|`* * *` |user |add a new person | +|`* * *` |user |create a new <> | + +|`* * *` |user |delete a deck |remove decks that I no longer need + +|`* * *` |user |list all decks |check what decks I can choose from to use + +|`* * *` |user |add a <> to a deck |add words that I want to practice with + +|`* * *` |user |delete a card from a deck|remove words that I no longer want to practice with + +|`* * *` |user |show both sides of a card|check the translation of a word + +|`* * *` |user |show all cards in a deck (view deck) | + +|`* * *` |user |edit a card in a deck |update or enhance the content of a card + +|`* * *` |user |delete all decks |start afresh with a clean slate program + +|`* * *` |user |delete all cards in a deck |start afresh with a clean deck of the same name + +|`* * *` |user |exit the program by typing | exit the program without using the mouse + +|`* *` |user |have a <> |memorize new words even more effectively + +|`* *` |user |find a deck by name |locate the deck without having to go through the entire list of decks -|`* * *` |user |delete a person |remove entries that I no longer need +|`* *` |user |find a specific card by name in any language |locate the card without having to go through the entire list of decks and cards -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +|`* *` |user |be able to choose which side of the card to see first |have two ways of memorizing new words. -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +|`* *` |user |keep track of how many cards I have visited | so that I can see my progress of learning a deck + +|`*` |user |clone a deck of cards |so that I can create custom sets of decks from existing decks quickly + +|`*` |user |add audio files to cards |add more information such as the correct pronunciation to the card + +|`*` |user |choose to have a card I appear more times |have cards that are harder to memorize appear more frequently + +|`*` |user |timer for going through a deck |see how much time it took me to memorize a deck of cards -|`*` |user with many persons in the address book |sort persons by name |locate a person easily |======================================================================= -_{More to be added}_ +// end::appendixB[] [appendix] == Use Cases -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +(For all use cases below, the *System* is the `FlashSpeed` and the *Actor* is the `user`, unless specified otherwise) + +[discrete] +=== UC01: Help + +*MSS:* + +1. User requests help. +2. FlashSpeed pops up a new small window and shows all possible commands and their usage. ++ +Use case ends. + +{empty} + + +[discrete] +=== UC02: Create a new deck + +*MSS:* + +1. User requests to create a deck of a certain name. +2. FlashSpeed creates a new deck and the deck shows up on the UI. ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The given name already exists. ++ +[none] +** 2a1. FlashSpeed shows an error message. ++ +Use case resumes at step 1. + +{empty} + +// tag::appendixC[] [discrete] -=== Use case: Delete person +=== UC03: Delete a deck -*MSS* +*MSS:* -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. FlashSpeed shows a list of decks. +2. User chooses a deck and deletes it. +3. The deck disappears from the list of decks. + Use case ends. *Extensions* [none] -* 2a. The list is empty. +* 1. The list is empty. ++ +Use case ends. + +* 2a. The given index is invalid. ++ +[none] +** 2a1. FlashSpeed shows an error message. ++ +Use case resumes at step 1. + +{empty} + + + + +[discrete] +=== UC04: View a deck (select) + +*MSS:* + +1. FlashSpeed shows a list of all decks. +2. Uer chooses a deck and requests to view that deck. +3. FlashSpeed shows a list of all cards in the deck. + Use case ends. -* 3a. The given index is invalid. +*Extensions* + +[none] +* 1. The list is empty. ++ +Use case ends. + +* 2a. The given index is invalid. ++ +[none] +** 2a1. FlashSpeed shows an error message. ++ +Use case resumes at step 1. +// end::appendixC[] +{empty} + + +[discrete] +=== UC05: Add a card to a deck + +*MSS:* + +1. FlashSpeed shows a list of decks. +2. User chooses a deck and requests to view that deck. +3. FlashSpeed shows a list of all cards in the deck. +4. User requests to add a specific card in the deck. +5. FlashSpeed adds the card and the card shows up in the deck. ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The list is empty. ++ +Use case ends. + +* 2a. The given deck index is invalid. ++ +[none] +** 2a1. FlashSpeed shows an error message. ++ +Use case resumes at step 1. + +* 4a. The deck already contains the same card the user requested to add. + [none] -** 3a1. AddressBook shows an error message. +** 4a. FlashSpeed shows an error message. + -Use case resumes at step 2. +Use case resumes at step 3. -_{More to be added}_ +{empty} + + +[discrete] +=== UC06: Delete a card from a deck + +*MSS:* + +1. FlashSpeed shows a list of decks. +2. User chooses a deck and requests to list all cards in that deck. +3. FlashSpeed shows a list of all cards in the deck. +4. User requests to delete a specific card in the deck. +5. FlashSpeed deletes the card and the card disappears from the deck. ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The list is empty. ++ +Use case ends. + +* 2a. The given index is invalid. ++ +[none] +** 2a1. FlashSpeed shows an error message. ++ +Use case resumes at step 1. + +[none] +* 3a. The deck is empty. ++ +Use case ends. + +[none] +* 4a. The given index is invalid. ++ +[none] +** 4a1. FlashSpeed shows an error message. ++ +Use case resumes at step 3. + +{empty} + + +[discrete] +=== UC07: Edit a card in a deck + +*MSS:* + +1. FlashSpeed shows a list of decks. +2. User chooses a deck and requests to list all cards in that deck. +3. FlashSpeed shows a list of all cards in the deck. +4. User requests to edit a specific card in the deck. +5. FlashSpeed edits the card. ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The list is empty. ++ +Use case ends. + +* 2a. The given index is invalid. ++ +[none] +** 2a1. FlashSpeed shows an error message. ++ +Use case resumes at step 1. + +[none] +* 3a. The deck is empty. ++ +Use case ends. + +[none] +* 4a. The given index is invalid. ++ +[none] +** 4a1. FlashSpeed shows an error message. ++ +Use case resumes at step 3. + +{empty} + + +[discrete] +=== UC08: Delete all decks + +*MSS:* + +1. User requests to delete all decks. +2. FlashSpeed deletes all decks. + +Use case ends. + +{empty} + + +[discrete] +=== UC09: Exit + +*MSS:* + +1. User requests to exit FlashSpeed. + +User case ends. + +{empty} + + +[discrete] +=== UC10: Playing a deck + +*MSS:* + +1. FlashSpeed shows a list of all decks. +2. User chooses a deck and requests to play that deck. +3. FlashSpeed changes into game view and starts the game. ++ +Use case ends. + +*Extensions* + +[none] +* 1. The list is empty. ++ +Use case ends. + +* 2a. The given index is invalid. ++ +[none] +** 2a1. FlashSpeed shows an error message. ++ +Use case resumes at step 1. + +{empty} + + +[discrete] +=== UC10: Playing a game. + +*MSS:* + +1. FlashSpeed shows a list of all decks. +2. User chooses a deck and requests to play that deck. +3. FlashSpeed changes into game view and starts the game. +4. FlashSpeed shows a card. +5. User flips the card. +6. FlashSpeed shows the other side of the card. +7. User types in yes or no to indicate the correctness of their answer. +8. FlashSpeed goes to next card. +9. Use case repeats from step 4 to step 7 until all cards are answered and the game ends. +10. FlashSpeed shows the statistics of game. ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The list is empty. ++ +Use case ends. + +* 2a. The given index is invalid. ++ +[none] +** 2a1. FlashSpeed shows an error message. ++ +Use case resumes at step 1. + +[none] +* 3a. The deck is empty. ++ +[none] +** 3a1. FlashSpeed shows an error message. +Use case ends. + +[none] +* 4a. The given index is invalid. ++ +[none] +** 4a1. FlashSpeed shows an error message. ++ +Use case resumes at step 3. [appendix] == Non Functional Requirements . Should work on any <> as long as it has Java `11` or above installed. -. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +. Should be able to hold up to 1000 decks without a noticeable sluggishness in performance for typical usage. . A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. -_{More to be added}_ +// tag::appendixE[] [appendix] == Glossary [[mainstream-os]] Mainstream OS:: -Windows, Linux, Unix, OS-X +Windows, Linux, Unix, macOS. + +[[deck]] Deck:: +A Deck is a group of Cards. + +[[card]] Card:: +A Card mimics a physical flashcard. It has two faces. One side for prompting the user and the other side for the content the user wants to memorize. + +[[smart-review-system]] Smarter Review System:: +The smarter review system is adopted from the well known <>. +Cards that are answered wrongly in Play Mode will be shown more frequently in this system. + +[[spaced-repetition-system]] Space Repetition System:: +Spaced repetition is an evidence-based learning technique that is usually performed with flashcards. Newly introduced and more difficult flashcards are shown more frequently while older and less difficult flashcards are shown less frequently in order to exploit the psychological spacing effect. + +// end::appendixE[] + +//tag::views[] +[[view]] View:: +The state or mode which FlashSpeed is in. +There are three different views in FlashSpeed. + +* <> +* <> +* <> + +[[library-view]] Library View:: +When no deck is selected and no cards are shown. + +.In Library view. No deck is selected. +image::library.png[] + +[[deck-view]] Deck View:: +When a deck is selected and its cards are shown. -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others +.In Deck view. A deck has been selected. +image::Ui.png[] + +[[play]] Play View:: +When in a study session of a deck. + +.In Play view. A deck is being studied. +image::Ui2.png[] + +//end::views[] [appendix] == Product Survey -*Product Name* +*Anki* -Author: ... +Author: Damien Elmes Pros: -* ... -* ... +* Study algorithm is useful for easily memorizing content. +* Extensive card browser Cons: -* ... -* ... +* Not as appealing UI. +* Not friendly for fast typist to execute commands/tasks (no CLI). [appendix] == Instructions for Manual Testing @@ -392,34 +1083,185 @@ These instructions only provide a starting point for testers to work on; testers .. Download the jar file and copy into an empty folder .. Double-click the jar file + - Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + Expected: Shows the GUI with a set of sample decks and cards. The minimum optimum window size for the app is already set. + +=== Help functionality +. Opening Help Window +.. Prerequisites: None +.. Test case: `help` + + Expected: Opens the help window +.. Test case: Pressing the `F1` key on the keyboard. + + Expected: Opens the help window + +. Closing the Help Window +.. Prerequisites: Help Window is opened +.. Test case: Pressing the `Alt + F4` keys on the keyboard. + + Expected: Closes the help window +.. Test case: Pressing the `X` button on the help window. + + Expected: Closes the help window + +. Reading off from where you last left off. +.. Open the help window. +.. Scroll down to any part of the User Guide to read. +.. Close the help window. +.. Re-launch the help window. + + Expected: The help window opens to the page you last left off from. + +// tag::appendixG[] +=== Deck functionality +. Creating a deck. + +.. Prerequisites: None +.. Test case: `create Russian` + + Expected: Deck shows up on the deck list on the left panel and the (currently empty) card list is shown on the right panel. +.. Test case: `create x` (where x is a deck name that already exists) + + Expected: No new deck is created. Error details shown in the status message. Status bar remains the same. +.. Other incorrect create commands to try: `create`, `create ` + Expected: Similar to previous + +. Deleting a deck from the deck list shown on the left panel. + +.. Prerequisites: Ensure that FlashSpeed contains at least 1 Deck, which can be seen on the left panel. +.. Test case: `remove 1` + + Expected: First deck is deleted from the list. Name of the deleted deck is shown in the status message. Timestamp in the status bar is updated. +.. Test case: `remove 0` + + Expected: No deck is deleted. Error details shown in the status message. Status bar remains the same. +.. Other incorrect delete commands to try: `remove`, `remove x` (where x is larger than the list size) + + Expected: Similar to previous. -. Saving window preferences +. Renaming a deck. -.. 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. +.. Prerequisites: Ensure that FlashSpeed contains at least 1 Deck, which can be seen on the left panel. +.. Test case: `rename 1 Russian` + + Expected: New deck name shows up on the deck list on the left panel. +.. Test case: `rename 0 Russian` + + Expected: No deck is renamed. Error details shown in the status message. Status bar remains the same. +.. Other incorrect create commands to try: `rename Russian`, `rename`, `rename x Russian` (where x is larger than the list size), + `rename 1 y` (where y is a deck name that already exists) + + Expected: Similar to previous. -_{ more test cases ... }_ +=== Card functionality +. Adding a card to a deck. -=== Deleting a person +.. Prerequisites: A deck needs to be selected first via `select INDEX` +.. Test case: `add 아녕하세요 : hello` + + Expected: Card shows up on the card list on the right panel. +.. Test case: `add 안녕:하세요:hello` + Expected: No card is created. Error details shown in the status message. Status bar remains the same. +.. Other incorrect create commands to try: `add 안녕하세요: `, `add :hello` + +Expected: Similar to previous -. Deleting a person while all persons are listed +. Deleting a card from the card list shown on the right panel. -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +.. Prerequisites: A deck needs to be selected first via `select INDEX` .. Test case: `delete 1` + - Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. + Expected: First card is deleted from the list. Name of the deleted card is 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: No card 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) + Expected: Similar to previous. -_{ more test cases ... }_ - -=== Saving data - -. Dealing with missing/corrupted data files +. Editing a card. + +.. Prerequisites: A deck needs to be selected first via `select INDEX` +.. Test case: `edit 1 안녕 : Hi!` + + Expected: New card information is reflected on the card list on the left panel. +.. Test case: `edit 1 안녕:` + + Expected: New card information (the front side) is reflected on the card list on the left panel. +.. Test case: `edit 1 : Hi!` + + Expected: New card information (the back side) is reflected on the card list on the left panel. +.. Test case: `edit 0 안녕하세요: Hi!` + + Expected: No card is edited. Error details shown in the status message. Status bar remains the same. +.. Other incorrect create commands to try: `edit`, `edit test : test test`, `edit x Russian : 러시안어` (where x is larger than the list size), + `rename 1 y` (where y is a card that already exists) + + Expected: Similar to previous. +// end::appendixG[] +=== Game-play functionality +. Starting a game +.. Prerequisites: Ensure that FlashSpeed contains at least 1 Deck, which can be seen on the left panel. +.. Test case: `play 1` + + Expected: First deck is chosen and play mode starts. +.. Test case: `play 0` + + Expected: No deck chosen and played. Error details shown in the status message. +.. Other incorrect delete commands to try: `play`, `play x` (where x is larger than the list size) + + Expected: Similar to previous. -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ +. Flipping a card in game +.. Prerequisites: FlashSpeed is in a game session and the current card has not been flipped yet. +.. Test case: `flip` + + Expected: The back side of the card is shown. +.. Test case: `flip` when the back side of the card is already shown + + Expected: Nothing happens. Error details shown in the status message. + +. Answering yes or no in game +.. Prerequisites: FlashSpeed is in a game session and the current card has already been flipped. +.. Test case: `yes` or `no` + + Expected: The next card is shown or the game ends. +.. Test case: `yes` or `no` when the card is not yet flipped. + + Expected: Nothing happens. Error details shown in the status message. + +. Ending a game +.. Prerequisites: FlashSpeed is in a game session. +.. Test case: `flip` + `yes` until the game ends + + Expected: A statistics screen will popup showing the statistics of the game. + + +=== Game statistics +. Display game statistics +.. Prerequisites: Ensure that FlashSpeed contains at least 1 Deck, which can be seen on the left panel. +.. Test case: +.... Start a game session with a deck. +.... Cycle through `flip`, `yes` or `no`. +.... Take note of the progress bar / counters / number of cards answered wrongly. +.... When the game end, the statistics of your game will show up. +.... Ensure that the numbers you have calculated / seen / taken note of tally up. -_{ more test cases ... }_ +[appendix] +== Effort +*Overview* +FlashSpeed, a flashcard application, is aimed specifically at helping University students with learning a new language. +FlashSpeed is significantly different from Address Book 3 (AB3) although it is based off of it. +In addition to changing existing features in AB3, we needed to add additional features that would complete our flashcard application. For example, the Play feature is a new feature +introduced in FlashSpeed. + +*Challenges and Effort* + +* *Card and Deck Model* + +AB3 contained a `person` class, which we wanted to refactor into our `card` class since each card contained information that +could be easily refactored from `person`. However, since we wanted it to be possible to have many cards in a deck and many decks in a library, +there was a design consideration of how we should implement `deck`. After much discussion, we decided to implement it as what can +be described as a "layered AB3" implementation, where in AB3, an addressbook contains persons, but now we have a bigger library that +contains multiple addressbooks. + +* *Play feature* + +Since this was a brand new feature that was created from scratch, +there were multiple suggestions from different members for +best way to implement it. After much discussion and some compromise from each design, we came out with our final implementation. Additionally, since +it was a new feature, even after we did the initial implementation, there were things that we did not foresee, such as how the UI would +handle the different views depending on the state of the game and the defensive code to implement to prevent bugs. + +* *User Interface Design* + +We were brainstorming for a good interface that could support the three views that we had: Library View, Deck View and Play View. +We managed to create about 2-3 mock-ups that everyone thought looked really good. +We then examined the User Experience (UX) advantages +between the mock-ups and decided ultimately that an enhanced AB3 layout was the best for our flashcard purposes. + +* *User Interface Implementation* + +As none of the members in our team were experienced with UI design, our initial mock-up and vision of the UI proved too difficult for us to implement within our time frame. +However, Robert (our UI Lead) focused on building and designing the UI while the rest of the team focused on the functional components of FlashSpeed. +Robert would ask us for input based on the experimental UIs that he had designed and improve it based on our suggestions. + + +* *Design Considerations* + +For each major enhancement, there were always a few design alternatives to choose from. As each team member felt that their design considerations had merit, +in our first major enhancement meeting, we spent 2 hours deciding on the initial design implementation. After that meeting, our team decided +on a faster way to resolve such cases by spending a maximum of 30 minutes discussing before casting a vote. It worked well for the +future meetings. + +*Achievements* + +. We managed to create FlashSpeed to what we envisioned it to be with the features we wanted. +. Although we did not manage to implement all the user-stories that we brain-stormed, we implemented all the essential functionality of FlashSpeed plus a few more nice features. +. FlashSpeed can safely support most digitized written languages in the world, due to our stringent parser checks and language support from Java. +. We learnt a lot about important processes and considerations in software development, e.g consistent coding style, version control skills, documentation writing, extensive testing, etc. + +// end::appendix[] diff --git a/docs/Documentation.adoc b/docs/Documentation.adoc index ad90ac87bda..39cadd3f084 100644 --- a/docs/Documentation.adoc +++ b/docs/Documentation.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Documentation += FlashSpeed - Documentation :site-section: DeveloperGuide :toc: :toc-title: @@ -12,7 +12,7 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S2-CS2103T-W17-1/main == Introduction diff --git a/docs/HelpWindow.adoc b/docs/HelpWindow.adoc new file mode 100644 index 00000000000..bfdae99198d --- /dev/null +++ b/docs/HelpWindow.adoc @@ -0,0 +1,3 @@ +:no-site-header: + +include::UserGuide.adoc[] diff --git a/docs/LearningOutcomes.adoc b/docs/LearningOutcomes.adoc deleted file mode 100644 index 436c1777617..00000000000 --- a/docs/LearningOutcomes.adoc +++ /dev/null @@ -1,216 +0,0 @@ -= Learning Outcomes -:site-section: LearningOutcomes -:toc: macro -:toc-title: -:toclevels: 1 -:imagesDir: images -:stylesDir: stylesheets -ifdef::env-github[] -:note-caption: :information_source: -endif::[] - -After studying this code and completing the corresponding exercises, you should be able to, - -toc::[] - -== Utilize User Stories `[LO-UserStories]` - -=== References - -* https://se-edu.github.io/se-book/specifyingRequirements/userStories/[se-edu/se-book: Requirements: Specifying Requirements: User Stories] - -=== Exercise: Add more user stories - -* Assume you are planing to expand the functionality of the AddressBook (but keep it as a CLI application). -What other user stories do you think AddressBook should support? Add those user stories to the `DeveloperGuide.adoc`. - -== Utilize use cases `[LO-UseCases]` - -=== References - -* https://se-edu.github.io/se-book/specifyingRequirements/useCases/[se-edu/se-book: Requirements: Specifying Requirements: Use Cases] - -=== Exercise: Add a 'Rename tag' use case - -* Add a use case to the `DeveloperGuide.adoc` to cover the case of _renaming of an existing tag_. -e.g. rename the tag `friends` to `buddies` (i.e. all persons who had the `friends` tag will now have -a `buddies` tag instead) -Assume that AddressBook confirms the change with the user before carrying out the operation. - -== Use Non Functional Requirements `[LO-NFR]` - -=== References - -* https://se-edu.github.io/se-book/requirements/nonFunctionalRequirements/[se-edu/se-book: Requirements: Non-Functional Requirements] - -=== Exercise: Add more NFRs - -* Add some more NFRs to the `DeveloperGuide.adoc` - -== Use Polymorphism `[LO-Polymorphism]` - -Note how the `Command::execute()` method shows polymorphic behavior. - -=== References - -* https://se-edu.github.io/se-book/oop/polymorphism/[se-edu/se-book: Paradigms: OOP: Polymorphism] -* https://se-edu.github.io/se-book/cppToJava/inheritance/polymorphism/[se-edu/se-book: C++ to Java: OOP: Polymorphism] - -=== Exercise: Add a polymorphic `isMutating` method - -* Add a method `boolean isMutating()` to the `Command` class. This method will return `true` for -command types that mutate the data. e.g. `AddCommand` -* Currently, AddressBook data are saved to the file after every command. -Take advantage of the the new method you added to limit file saving to only for command types that mutate data. -i.e. `add` command should always save the data while `list` command should never save data to the file. - -[NOTE] -==== -There may be better ways to limit file saving to commands that mutate data. The above approach, while not -optimal, will give you chance to implement a polymorphic behavior. -==== - -== Use abstract classes/methods `[LO-Abstract]` - -=== References - -* https://se-edu.github.io/se-book/oop/inheritance/abstractClasses/[se-edu/se-book: Paradigms: OOP: Abstract Classes] -* https://se-edu.github.io/se-book/cppToJava/inheritance/abstractClassesAndMethods/[se-edu/se-book: C++ to Java: OOP: Abstract Classes] - -=== Exercise: Make `Command#execute()` method abstract - -* Make the `Command#execute()` method abstract (hint: refer to the comment given below the method) - -== Use interfaces `[LO-Interfaces]` - -Note how the `AddressBook` class implements the `ReadOnlyAddressBook` interface so that clients who don't need write access to the `AddressBook` can access the `AddressBook` through the `ReadOnlyAddressBook` interface instead. - -image::ReadOnlyAddressBookUsage.png[width=500] - -=== References - -* https://se-edu.github.io/se-book/oop/inheritance/interfaces/[se-edu/se-book: Paradigms: OOP: Abstract Interfaces] -* https://se-edu.github.io/se-book/cppToJava/inheritance/interfaces/[se-edu/se-book: C++ to Java: OOP: Abstract Interfaces] - -=== Exercise: Add a `Printable` interface - -* Add a `Printable` interface as follows. -+ -image::PrintableInterface.png[width=400] -* `Override` the `getPrintableString` in classes `Name`, `Phone`, `Email`, and `Address` so that each produces a printable string representation of the object. e.g. `Name: John Smith`, `Phone: 12349862` -* Add the following method in a suitable place of some other class. Note how the method depends on the Interface. -+ -[source,java] ----- -/** - * Returns a concatenated version of the printable strings of each object. - */ -String getPrintableString(Printable... printables) { ----- -+ -The above method can be used to get a printable string representing a bunch of person details. -For example, you should be able to call that method like this: -+ -[source,java] ----- -// p is a Person object -return getPrintableString(p.getPhone(), p.getEmail(), p.getAddress()); ----- - -== Follow Liskov Substitution Principle `[LO-LSP]` - -=== References - -* https://se-edu.github.io/se-book/principles/liskovSubstitutionPrinciple/[se-edu/se-book: Principles: Liskov Substitution Principle] - -=== Exercise: Add an exception to an overridden method - -* Add a `throws Exception` clause to the `AddCommand::execute` method. Notice how Java compiler will not allow it, -unless you add the same `throws` clause to the parent class method. This is because if a child class throws -an exception that is not specified by the Parent's contract, the child class is no longer substitutable in place of -the parent class. -* Also note that while in the above example the compiler enforces LSP, there are other situations where it is up to -the programmer to enforce it. For example, if the method in the parent class works for `null` input, the overridden -method in the child class should not reject `null` inputs. This will not be enforced by the compiler. - -== Use Java-FX for GUI programming `[LO-JavaFx]` - -=== References - -* https://se-edu.github.io/se-book/javaTools/javaFXBasic/[se-edu/se-book: Tools: Java: JavaFX: Basic] - -=== Exercise: Enhance GUI - -* Do some enhancements to the AddressBook GUI. e.g. add an application icon, change font size/style - -== Analyze Coupling and Cohesion of designs `[LO-CouplingCohesion]` - -* Notice how having a separate `ParserUtil` class to handle user input validation, space trimming etc. of model data (an application of the Single Responsibility Principle) improves the _cohesion_ of the model component (since it does not need to be concerned with handling user input) as well as the `ParserUtil` class. - -=== References - -* https://se-edu.github.io/se-book/designFundamentals/coupling/[se-edu/se-book: Design: Design Principles: Coupling] -* https://se-edu.github.io/se-book/designFundamentals/cohesion/[se-edu/se-book: Design: Design Principles: Cohesion] - -=== Exercise: Identify places to reduce coupling and increase cohesion - -* Where else in the design coupling can be reduced further, or cohesion can be increased further? - -[[apply-dependency-inversion-principle-lo-dip]] -== Apply Dependency Inversion Principle `[LO-DIP]` - -* Note how the `LogicManager` class doesn't depend on `StorageManager` directly, but rather the interface `Storage`. -This is an application of the Dependency Inversion Principle. -+ -image::LogicStorageDIP.png[width=300] -* Where else in the code do you notice the application of DIP? - -=== References - -* https://se-edu.github.io/se-book/principles/dependencyInversionPrinciple/[se-edu/se-book: Principles: Dependency Inversion Principle] - -== Use Dependency Injection `[LO-DI]` - -Notice how the `LogicManager` class does not depend on the `StorageManager` class, but depends on the `Storage` interface. -This allows us to use _Dependency Injection_ to test the `LogicManager` class without getting the `StorageManager` class involved. - -=== References - -* https://se-edu.github.io/se-book/testing/dependencyInjection/[se-edu/se-book: Quality Assurance: Testing: Dependency Injection] - -=== Exercise: Facilitate injecting a StorageStub - -* Notice how `LogicManagerTest` tests `LogicManager` by constructing a `StorageManager` object. -* Implement `StorageStub` such that calls to its `save*` methods do nothing (i.e. empty method body). -* Update `LogicManagerTest` to work with the `StorageStub` instead of the actual `StorageManager` object. -i.e. `LogicManagerTest` injects a `StorageStub` object when constructing a `LogicManager` before testing it. -+ -image::DependencyInjection.png[width=600] -* The example above uses <> as a means to achieve DI. -Note that there is another way to inject a `StorageStub` object, as shown below. -In this case we do not apply the DIP but we still achieve DI. -+ -image::DependencyInjectionWithoutDIP.png[width=250] - -== Apply Open-Closed Principle `[LO-OCP]` - -=== References - -* https://se-edu.github.io/se-book/principles/openClosedPrinciple/[se-edu/se-book: Principles: Open-Closed Principle] - -=== Exercise: Analyze OCP-compliance of the `LogicManager` class - -* Consider adding a new command to the Address Book. e.g. an `edit` command. Notice how little you need to change in the `LogicManager` class to extend its behavior so that it can execute the new command. -That is because `LogicManager` follows the OCP i.e. `LogicManager` is _open to be extended_ with more commands but _closed for modifications_. -* Is it possible to make the `AddressBookParser` class more OCP-compliant in terms of extending it to handle more -command types? -* In terms of how it saves data, is `LogicManager` more OCP-compliant -due to the application of DIP as given in <>? -How can you improve ``LogicManager``'s OCP-compliance further so that it can not only work with different types -of storages, but different number of storages (e.g. save to both a text file and a database). - -== Work in a 3KLoC code base `[LO-3KLoC]` - -=== Exercise: Enhance AddressBook - -* Enhance AddressBook in some way. e.g. add a new command diff --git a/docs/SettingUp.adoc b/docs/SettingUp.adoc index c0659782fab..7339ff2d0e1 100644 --- a/docs/SettingUp.adoc +++ b/docs/SettingUp.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Setting Up += FlashSpeed - Setting Up :site-section: DeveloperGuide :toc: :toc-title: @@ -12,7 +12,7 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S2-CS2103T-W17-1/main == Prerequisites @@ -37,7 +37,7 @@ Do not disable them. If you have disabled them, go to `File` > `Settings` > `Plu == Verifying the setup -. Run the `seedu.address.Main` and try a few commands +. Run the `Main` and try a few commands . <> to ensure they all pass. == Configurations to do before writing code @@ -57,9 +57,9 @@ 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-level3` repo. +After forking the repo, the documentation will still have the FlashSpeed Foundation branding and refer to the `AY1920S2-CS2103T-W17-1/main` repo. -If you plan to develop this fork as a separate product (i.e. instead of contributing to `se-edu/addressbook-level3`), you should do the following: +If you plan to develop this fork as a separate product (i.e. instead of contributing to `AY1920S2-CS2103T-W17-1/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. @@ -81,4 +81,4 @@ Having both Travis and AppVeyor ensures your App works on both Unix-based platfo === Getting started with coding -When you are ready to start coding, we recommend that you get some sense of the overall design by reading about <>. +When you are ready to start coding, we recommend that you get some sense of the overall design by reading about <>. diff --git a/docs/Testing.adoc b/docs/Testing.adoc index 5767b92912c..e92603e0b30 100644 --- a/docs/Testing.adoc +++ b/docs/Testing.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 3 - Testing += FlashSpeed - Testing :site-section: DeveloperGuide :toc: :toc-title: @@ -12,7 +12,7 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3/tree/master +:repoURL: https://github.com/AY1920S2-CS2103T-W17-1/main == Running Tests @@ -35,11 +35,11 @@ See <> for more info on how to run tests using G We have three types of tests: . _Unit tests_ targeting the lowest level methods/classes. + -e.g. `seedu.address.commons.StringUtilTest` +e.g. `com.flashspeed.commons.util.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. `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. `LogicManagerTest` == Troubleshooting Testing diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 4e5d297a19f..98c03326835 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,6 +1,7 @@ -= AddressBook Level 3 - User Guide += FlashSpeed - User Guide :site-section: UserGuide :toc: +:toclevels: 3 :toc-title: :toc-placement: preamble :sectnums: @@ -12,166 +13,610 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level3 +:repoURL: https://github.com/AY1920S2-CS2103T-W17-1/main -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `FlashSpeed Foundation` Since: `Feb 2020` Licence: `MIT` +// tag::intro[] == Introduction -AddressBook Level 3 (AB3) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB3 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +FlashSpeed is a text-based flashcard application specifically designed for university students who are learning a foreign language. University students often have hectic schedules. With this in mind, FlashSpeed was created to allow students to be able to study and revise foreign vocabulary on the fly. With a single command, you can start a quick study session on FlashSpeed whenever! +Keeping, flipping, and tracking physical flashcards can be a pain. FlashSpeed enhances the studying process by having virtual flashcards and a smarter review system. Users will be tested more frequently on flashcards that they had trouble memorizing previously. By using FlashSpeed, you will learn faster and remember for longer! + +This guide will walk you through the many exciting features of FlashSpeed as well as give step-by-step instructions on how to use them. Helpful tips and notes are also provided! + + +We hope you enjoy using our app. Happy FlashSpeeding! + +== Language Support + +FlashSpeed can support almost every popular language and their associated script. You can even use emojis in your cards! 😍 + + +You can use FlashSpeed to study: + + +- Japanese +- Korean +- French +- Spanish +- German +- Hindi +- Russian +- ...and many more! + +[NOTE] +The display of the characters may be limited to your own operating systems's language support. Please refer to +your operating system's manual or user guide to find out the languages and scripts supported. + +[NOTE] +Technical info: FlashSpeed, which is built with Java, supports the Unicode standard with UTF-16 character encoding for +textual representation. + +// end::intro[] == Quick Start -. Ensure you have Java `11` or above installed in your Computer. -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. -. Double-click the file to start the app. The GUI should appear in a few seconds. -+ -image::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. +Getting FlashSpeed up and running is fast and easy. Follow these simple steps to get started! + +. Ensure you have Java `11` installed in your computer. +. Download the latest `flashspeed.jar` file link:https://github.com/AY1920S2-CS2103T-W17-1/main/releases[here]. This is the entire FlashSpeed app in one convenient package. +. Copy `flashspeed.jar` to a folder of your choice. This will be FlashSpeed's home folder. All files created by FlashSpeed will be automatically stored here. +. Double-click `flashspeed.jar` to start it. FlashSpeed will appear in a few seconds. +. Type a 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: -* *`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 +* **`create`**`Japanese 1` : creates a deck named `Japanese 1` in the library +* **`remove`**`3` : removes the 3rd deck shown in the decks list * *`exit`* : exits the app -. Refer to <> for details of each command. +. Refer to <> for details of each command or <> for a condensed view of all commands. [[Features]] == Features ==== -*Command Format* +*Command Format and Input* -* 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 `<>` are the command parameters to be supplied by you, + +e.g. for the command format of `create `, `` is a parameter of your choice, such as `create Japanese 1`. +* All *parameters* will have any leading and trailing whitespace removed before being processed, + +e.g. `` is equal to ` `. ==== -=== Viewing help : `help` +//tag::views[] +==== +*Different Views* -Format: `help` +A view is simply the state or mode of FlashSpeed you are in now. +You will be able to identify the view you are in from what you are currently seeing in FlashSpeed. + +[NOTE] +Some commands only work in certain views. Don't worry, this guide will tell you all you need to know! + +FlashSpeed can be in one of 3 different views, namely: + +* *Library view:* when no deck is selected and no cards are shown + +.In Library view. No deck is selected. +image::library.png[] + +{empty} + + +* *Deck view:* when a deck is selected and its cards are shown + +.In Deck view. A deck has been selected. +image::Ui.png[] + +{empty} + + +* *Play view:* when in a study session of a deck -=== Adding a person: `add` +.In Play view. A deck is being studied. +image::Ui2.png[] -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +==== + +//end::views[] + +{empty} + + +=== General + +==== Viewing help : `help` + +Format: `help` + +You can view this user guide in a new popup window by +typing `help` in the input box and pressing kbd:[Enter]. + [TIP] -A person can have any number of tags (including 0) +You can close the help window either by clicking on the close button +or pressing kbd:[Alt]+kbd:[F4] while the help window is in focus. -Examples: +{empty} + + +==== Exiting the program : `exit` + +Format: `exit` -* `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` +You can exit FlashSpeed by typing `exit` in the input box +and pressing kbd:[Enter]. Bye! + -=== Listing all persons : `list` +{empty} + -Shows a list of all persons in the address book. + -Format: `list` +==== Resetting the library : `reset` -=== Editing a person : `edit` +Format: `reset` -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +If you want to delete everything in the library to start fresh, you can choose to reset the library. +Simply type `reset` in the input box and press kbd:[Enter]. + + +[NOTE] +You cannot reset the library while in Play view. + +.After resetting. A new start! +image::reset.png[] + +{empty} + + +// tag::library-view[] +=== Library view **** -* 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. +[NOTE] +**Even though the commands in this section are mainly library based, you can use them in both the Library view and Deck view!** + +.No deck selected. You're now in Library view. +image::library.png[] **** +// end::library-view[] + +{empty} + + +// tag::createdeck[] + +==== Creating a deck : `create` + +Format: `create ` +**** +* *Formal definition*: Creates a deck with the deck name specified by the user. The deck name cannot be empty. +**** + +After downloading and setting up FlashSpeed, you're all set to go! +But before anything else, you will have to first *create a deck*. +The process of creating a deck in FlashSpeed is easy. + +Let's say you want to create a deck to revise some Japanese verbs +to prepare for your upcoming test. +To do so: + +. Firstly, type `create` followed by the deck name into the input box. + +* e.g. `create Japanese Verbs` ++ +.Typing the command to create the Japanese Verbs deck. +image::create1.png[] +. Press kbd:[Enter]. +. Voila! ++ +.After creating the Japanese Verbs deck. +image::create2.png[] + +{empty} + +// end::createdeck[] + +// tag::select-deck[] + +[[selectDeck]] +==== Selecting a deck : `select` + +Format: `select ` +**** +* *Formal definition*: +Selects and shows all cards in the deck at the specified `index`. +The `index` refers to the index number shown in the displayed decks list. The `index` *must be a positive integer* 1, 2, 3, ... +**** + +If you want to view the cards in a particular deck, you can use `select` followed by the deck's index number. +The index number is the number to the left of its name. +After selecting a deck, FlashSpeed will enter the Deck view and +show all the cards in that deck. + + +In the Deck view, you can use any of the Deck mode commands listed in <>. + +Example: + +* `select 1` + +Selects and shows all cards in the 1st deck. + +.Selecting a deck and showing its cards. +image::Ui.png[] + +// end::select-deck[] +{empty} + + +==== Removing a deck : `remove` + +Format: `remove ` +**** +* *Formal definition*: Removes the deck in the library at the specified `index`. +The `index` refers to the index number shown in the displayed decks list. +The `index` *must be a positive integer* 1, 2, 3, ... +**** + +Similarly, you can remove a deck in the library using `remove` followed by the index number of that deck. Bye bye, deck! + +Examples: + +* `remove 2` + +Removes the 2nd deck from the library. + +.After removing the Japanese Verbs deck. It's gone. +image::remove.png[] + +{empty} + + +// tag::renameAndPlay[] +==== Renaming a deck : `rename` + +Format: `rename ` +**** +* *Formal definition*: Renames the deck in the library at the specified `index`. The `index` refers to the index number shown in the displayed decks list. The `index` *must be a positive integer* 1, 2, 3, ... +**** + +You can rename a deck in the library using `rename` followed by the index number of the deck and the new name of the deck. + + +[NOTE] +The deck name cannot by empty. + 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. +* `rename 2 Japanese Verbs` + +Renames the 2nd deck in the library to "Japanese Verbs". + +.After renaming a deck from "Korean" to "Japanese Verbs". Wow, what a change. +image::rename.png[] + +{empty} + + +[[playDeck]] +==== Playing a deck : `play` + +Format: `play ` +**** +* *Formal definition*: Starts a study session with the deck in the library at the specified `index`. The `index` refers to the index number shown in the displayed decks list. The `index` *must be a positive integer* 1, 2, 3, ... +**** + +Alright, this is what you've been waiting for! In order to start a study session with a certain deck, +type `play` followed by the deck's index number and press kbd:[Enter]. Good luck on your learning journey. +FYI: we "play" a deck because learning is fun! + + +Example: + +* `play 1` + +Starts a study session with the 1st deck in the library. + +.Studying/Playing the Japanese deck. +image::Ui2.png[] -=== Locating persons by name: `find` +// end::renameAndPlay[] -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +{empty} + +[[deckViewAnchor]] +=== Deck view + +**** +[NOTE] +**Important: All commands in this section can only be used in Deck view.** +**To enter Deck view, you must <>.** + +.After selecting a deck. You're now in Deck view. +image::Ui.png[] +**** + +{empty} + + +// tag::addcard[] + +[#_adding_a_card_add] +==== Adding a card : `add` + +Format: `add :` +**** +* *Formal definition*: Creates a card and adds it to a deck, +with the `front` and `back` values of the card specified by the user. +Both the `front` and `back` values cannot be empty. +**** +**** +* *One and only one* colon (":") can be used in this command. +Since a colon is used as the separator, there should not be any colons in the `front` or `back` values. +**** + +Alright, after creating a new deck and giving it a great name, what's next? +*Adding cards* into the deck, of course! + +Once again, the process is easy: + +. Select the deck to which you want to add cards, with the `select` command. ++ +e.g. `select 1` + +. Inside the input box, type: +* `add`, followed by +* the word/sentence that you want as the `front` of the card, then +* a colon ":" right after, and finally +* the word/sentence that you want as the `back` of the card. ++ +e.g. `add ありがとう:thanks` + +. Press kbd:[Enter]. +. Voila! ++ +.Adding a new card into the Japanese deck. +image::add.png[] + +{empty} + + +// end::addcard[] + +==== Editing a card : `edit` + +Format 1: `edit :` + +Format 2: `edit :` + +Format 3: `edit :` +**** +* *Formal definition*: Edits the card in the current deck at the specified `index`. The `index` refers to the index number shown in the displayed cards list. The `index` *must be a positive integer* 1, 2, 3, ... +* Existing values will be updated to the given values. +* Empty values for the `front` or `back` will leave the original `front` or `back` intact. +**** **** -* 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` +* *One and only one* colon (":") can be used in this command. +Since a colon is used as the separator, there should not be any colons in the `front` or `back` values. **** +You can edit the front and/or back values of any card in the current deck. +The way to do so is similar to the <<_adding_a_card_add, `add`>> command as shown before. + + +Type `edit`, followed by the index number of the card to be edited, then the new `front` value, then a colon (":"), and finally the new `back` value. + + +[TIP] +You don't have to supply both the `front` and `back` values of a card if you only want to change one of them, +i.e. if you only wish to change the `back` of a card, `front` can be left blank, and vice versa. + Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `edit 2 ありがとう:thanks` + +Edits the front and back values of the 2nd card in the deck to be `ありがとう` and `thanks` respectively. +* `edit 2 :thanks` + +Edits the back value of the 2nd card in the deck to be `thanks`. +* `edit 2 ありがとう:` + +Edits the front value of the 2nd card in the deck to be `ありがとう`. + +.Editing the 2nd card's back value from "thank you" to "thanks". +image::edit.png[] -// tag::delete[] -=== Deleting a person : `delete` +{empty} + -Deletes the specified person from the address book. + -Format: `delete INDEX` +==== Deleting a card : `delete` +Format: `delete ` **** -* 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, ... +* *Formal definition*: Deletes the card in the current deck at the specified `index`. The `index` refers to the index number shown in the displayed cards list. The `index` *must be a positive integer* 1, 2, 3, ... **** +You can delete a card from the selected deck by using `delete` followed by the index number of that card. + + 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. +* `delete 3` + +Deletes the 3rd card in the current deck. -// end::delete[] -=== Clearing all entries : `clear` +.After deleting the 3rd card in the current deck. It's not there anymore. +image::delete.png[] -Clears all entries from the address book. + -Format: `clear` +{empty} + -=== Exiting the program : `exit` +==== Returning to the library : `return` -Exits the program. + -Format: `exit` +Format: `return` + +Once you're done viewing or modifying the selected deck, +you can return to the Library view with `return`, i.e. no deck will be selected. + + +.Returned to the Library view. No deck selected! +image::library.png[] + +{empty} + + + +=== Play view + +Format: `play ` + +**** +[NOTE] +**Important: All commands in this section can only be used in Play view.** +**To enter Play view, you must <>.** + +// tag::play-view-language[] + +This is what you will see when you test yourself. +Strengthen your memory by frequently reviewing your cards! +When playing a deck, each card will initially only show its front face to allow you to recall its associated back face. + +// end::play-view-language[] + +.Playing the Japanese deck. がんばってください。 Ganbatte kudasai! +image::Ui2.png[] +**** -=== Saving the data +{empty} + -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::flip-language[] +==== Flipping a card : `flip` -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +Format: `flip` -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +Are you ready to reveal the back face of the card? +Let's flip it to check if your memory is spot on. +To flip a card, simply type `flip` into the input box and press kbd:[Enter]. + +// end::flip-language[] +.Flipping to reveal the back face of the card. Did you get it right? +image::flip.png[] + +{empty} + + +// tag::answer-language[] +==== Answering : `yes`/`no` + +Format: `yes` or `no` + +Were you able to recall the correct back value? + +If you could, type `yes` into the input box and press kbd:[Enter]. Congratulations! + +If you could not or your guess was incorrect, type `no` into the input box and press kbd:[Enter]. Don't give up! + + +// end::answer-language[] +[NOTE] +You can only answer after flipping the card. + +[NOTE] +If you answered `no` for a card, you will have an opportunity to see it again later in the same session. +FlashSpeed optimizes for cards you find difficult. + +.The next card (if any) will be immediately shown after answering. +image::yes.png[] + +{empty} + + +// tag::stop-language[] +==== Stopping a session: `stop` + +Format: `stop` + +A play session will end automatically when there are no more cards to review. +However, you can also stop an ongoing session immediately by typing `stop` into the input box and pressing kbd:[Enter]. +But of course, try your best and don't use this too often! + +// end::stop-language[] +[NOTE] +The session statistics will be shown either after completing a session or after manually stopping. + +[TIP] +You can close the statistics window either by clicking on the close button +or pressing kbd:[Alt]+kbd:[F4] while the statistics window is in focus. + +.Statistics shown after manually stopping the session. You will also see it if you complete a session. +image::stop.png[] + +{empty} + + +=== Save data + +All data in FlashSpeed (e.g. decks, cards) is saved automatically. It is all neatly placed in the folder you put FlashSpeed in. + +You don't have to worry about saving your precious flashcards manually! + +//tag::faq[] == 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. +*Q*: Does this application require an Internet connection? + +*A*: No, FlashSpeed does not require an Internet connection to use. + +*Q*: What is the maximum length of text I can enter into a flashcard? + +*A*: The flashcard method of studying benefits from succinct and concise flashcards. +Even though FlashSpeed does not limit the maximum length of text that can be entered and stored, +it will only show the text up to the size of the available display space. +Therefore, we recommend keeping any text *under 60 characters*. + +*Q*: How do I save my data? + +*A*: FlashSpeed automatically saves your data whenever you make a change. There is no need to save manually. + +*Q*: Will my data be sent anywhere else or shared with third parties? + +*A*: Your data is stored locally on your own computer. +FlashSpeed does not use any Internet connection so no data can be sent to any online servers. + +*Q*: How do I transfer my data to another computer? + +*A*: Simply copy the `data` folder in FlashSpeed's home folder over to the home folder in the other computer. +*Q*: How do I update FlashSpeed to the latest version when there is an update? + +*A*: You can check for any updates to FlashSpeed link:https://github.com/AY1920S2-CS2103T-W17-1/main/releases[here]. +You can then follow the same instructions as found in <>. + +*Q*: I am not able to run this application. What can I do? + +*A*: Refer to <> for the installation guide. Ensure that your computer has Java 11 installed. FlashSpeed may not be able to run on other versions of Java. +Alternatively, you can contact us https://ay1920s2-cs2103t-w17-1.github.io/main/ContactUs.html[here] for any further help. + +//end::faq[] + +[[commandSummary]] == 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` + +* *Help* : `help` + +* *Exit* : `exit` + +* *Reset* : `reset` + +* *Select* : `select ` + +e.g. `select 2` +* *Create* : `create ` + +e.g. `create Japanese 1` +* *Remove* : `remove ` + +e.g. `remove 2` +* *Rename* : `rename ` + +e.g. `rename 2 Japanese Verbs` +* *Play* : `play ` + +e.g. `play 2` + +* *Add* `add :` + +e.g. `add ありがとう:thanks` +* *Edit* : `edit :` or `edit :` or `edit :` + +e.g. `edit 1 ありがとう:thanks` or `edit 1 :thanks` or `edit 1 ありがとう:` +* *Delete* : `delete ` + 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` +* *Return* : `return` + +* *Flip* : `flip` +* *Yes* : `yes` +* *No* : `no` +* *Stop* : `stop` + +// tag::command-table[] +[width="60%",cols="30%,10%,10%,10%",options="header",] +|======================================================================= +| 3+|View +|Command |Library |Deck |Play +|`help` |√ |√ |√ + +|`exit` |√ |√ |√ + +|`reset` |√ |√ | + +|`select` |√ |√ | + +|`create` |√ |√ | + +|`remove` |√ |√ | + +|`rename` |√ |√ | + +|`play` |√ |√ | + +|`add` | |√ | + +|`edit` | |√ | + +|`delete` | |√ | + +|`return` | |√ | + +|`flip` | | |√ + +|`yes` | | |√ + +|`no` | | |√ + +|`stop` | | |√ + +|======================================================================= + +// end::command-table[] diff --git a/docs/UsingAppVeyor.adoc b/docs/UsingAppVeyor.adoc index 12a7a89ac68..8adeeafe2d1 100644 --- a/docs/UsingAppVeyor.adoc +++ b/docs/UsingAppVeyor.adoc @@ -6,12 +6,6 @@ ifdef::env-github[] :note-caption: :information_source: endif::[] -[NOTE] -==== -This document was originally written for _AddressBook Level 4_ and hence its screenshots refer to `addressbook-level4`. -For use with _AddressBook Level 3_, wherever `addressbook-level4` is used in the screenshots, you should use *`addressbook-level3`*. -==== - https://www.appveyor.com/[AppVeyor] is a _Continuous Integration_ platform for GitHub projects. It runs its builds on Windows virtual machines. AppVeyor can run the project's tests automatically whenever new code is pushed to the repo. This ensures that existing functionality and features have not been broken on Windows by the changes. diff --git a/docs/UsingCheckstyle.adoc b/docs/UsingCheckstyle.adoc index a12ab09cc9c..d3a7650f34b 100644 --- a/docs/UsingCheckstyle.adoc +++ b/docs/UsingCheckstyle.adoc @@ -8,12 +8,6 @@ ifdef::env-github[] :note-caption: :information_source: endif::[] -[NOTE] -==== -This document was originally written for _AddressBook Level 4_ and hence its screenshots refer to `addressbook-level4`. -For use with _AddressBook Level 3_, wherever `addressbook-level4` is used in the screenshots, you should use *`addressbook-level3`*. -==== - == Configuring Checkstyle-IDEA . Install the Checkstyle-IDEA plugin by going to `File` > `Settings` (Windows/Linux), or `IntelliJ IDEA` > `Preferences...` (macOS). + @@ -22,11 +16,11 @@ Restart the IDE to complete the installation. . Click `File` > `Settings...` > `Other Settings` > `Checkstyle` . Set `Scan Scope` to `Only Java sources (including tests)`, so that the plugin will run checkstyle for our test source codes as well . Ensure that the `Checkstyle version` is set to `8.1`. This is the same version that we are using inside Gradle, so that you won't get any errors due to version incompatibility - * If `Checkstyle version` is not set to `8.1`, set it to version `8.1` and click `Apply` +* If `Checkstyle version` is not set to `8.1`, set it to version `8.1` and click `Apply` + image::checkstyle-idea-scan-scope.png[width="500"] . Click the plus sign under `Configuration File` -. Enter an arbitrary description e.g. addressbook +. Enter an arbitrary description e.g. flashspeed . Select `Use a local Checkstyle file` . Use the checkstyle configuration file found at `config/checkstyle/checkstyle.xml` . Click `Next` > `Finish` diff --git a/docs/UsingCoveralls.adoc b/docs/UsingCoveralls.adoc index 5191e47316c..e70cb913172 100644 --- a/docs/UsingCoveralls.adoc +++ b/docs/UsingCoveralls.adoc @@ -6,12 +6,6 @@ ifdef::env-github[] :note-caption: :information_source: endif::[] -[NOTE] -==== -This document was originally written for _AddressBook Level 4_ and hence its screenshots refer to `addressbook-level4`. -For use with _AddressBook Level 3_, wherever `addressbook-level4` is used in the screenshots, you should use *`addressbook-level3`*. -==== - https://coveralls.io/[Coveralls] is a web service that tracks code coverage over time for GitHub projects. Coveralls requires Travis CI to be set up beforehand as Travis sends the coverage report from the latest build to Coveralls. If you have not set up Travis CI, see <>. Currently, Coveralls supports Travis CI but not AppVeyor. diff --git a/docs/UsingGradle.adoc b/docs/UsingGradle.adoc index cca9c6d1d12..0dc8b29cca3 100644 --- a/docs/UsingGradle.adoc +++ b/docs/UsingGradle.adoc @@ -42,7 +42,7 @@ When running a Gradle task, Gradle will try to figure out if the task needs runn == Creating the JAR file * *`shadowJar`* + -Creates the `addressbook.jar` file in the `build/jar` folder, _if the current file is outdated_. + +Creates the `flashspeed.jar` file in the `build/jar` folder, _if the current file is outdated_. + e.g. `./gradlew shadowJar` **** diff --git a/docs/diagrams/AddCardActivityDiagram.puml b/docs/diagrams/AddCardActivityDiagram.puml new file mode 100644 index 00000000000..9d62c685257 --- /dev/null +++ b/docs/diagrams/AddCardActivityDiagram.puml @@ -0,0 +1,18 @@ +@startuml +(*) --> "enter valid Add Card command " +If "" then +-> [has valid both front value] "create new Card with new front and back" +--> "replace Card to be edited with new Card" +else +-> [else] If "" then +--> [has front value only] "retrieve old back" +--> "create new Card with old back and new front" +--> "replace Card to be edited with new Card" +else +-> [has back value only] "retrieve old front" +--> "create new Card with old front and new back" +Endif +Endif +--> "replace Card to be edited with new Card" +--> (*) +@enduml diff --git a/docs/diagrams/AddSequenceDiagram.png b/docs/diagrams/AddSequenceDiagram.png new file mode 100644 index 00000000000..3a651385fbd Binary files /dev/null and b/docs/diagrams/AddSequenceDiagram.png differ diff --git a/docs/diagrams/AddSequenceDiagram.puml b/docs/diagrams/AddSequenceDiagram.puml new file mode 100644 index 00000000000..bfb15b54fa9 --- /dev/null +++ b/docs/diagrams/AddSequenceDiagram.puml @@ -0,0 +1,76 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager " as LogicManager LOGIC_COLOR +participant ":MasterParser " as MasterParser LOGIC_COLOR +participant ":AddCardCommandParser " as AddCardCommandParser LOGIC_COLOR +participant "r:AddCardCommand " as AddCardCommand LOGIC_COLOR +participant ":CommandResult " as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":Library " as Library MODEL_COLOR +end box + +[-> LogicManager : execute("add ありがとう:thanks") +activate LogicManager + +LogicManager -> MasterParser : parseCommand("add ありがとう:thanks") +activate MasterParser + +create AddCardCommandParser +MasterParser -> AddCardCommandParser +activate AddCardCommandParser + +AddCardCommandParser --> MasterParser +deactivate AddCardCommandParser + +MasterParser -> AddCardCommandParser : parse("ありがとう:thanks") +activate AddCardCommandParser + +create AddCardCommand +AddCardCommandParser -> AddCardCommand +activate AddCardCommand + +AddCardCommand --> AddCardCommandParser : r +deactivate AddCardCommand + +AddCardCommandParser --> MasterParser : r +deactivate AddCardCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +AddCardCommandParser -[hidden]-> MasterParser +destroy AddCardCommandParser + +MasterParser --> LogicManager : r +deactivate MasterParser + +LogicManager -> AddCardCommand : execute() +activate AddCardCommand + +AddCardCommand -> Model : addCard("ありがとう", "thanks") +activate Model + +Model -> Library : addCard("ありがとう", "thanks") +activate Library + +Library -> Model +deactivate Library + +Model --> AddCardCommand +deactivate Model + +create CommandResult +AddCardCommand -> CommandResult +activate CommandResult + +CommandResult --> AddCardCommand +deactivate CommandResult + +AddCardCommand --> LogicManager : result +deactivate AddCardCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/AnswerNoSequenceDiagram.puml b/docs/diagrams/AnswerNoSequenceDiagram.puml new file mode 100644 index 00000000000..5d51507c099 --- /dev/null +++ b/docs/diagrams/AnswerNoSequenceDiagram.puml @@ -0,0 +1,45 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":MasterParser" as MasterParser LOGIC_COLOR +participant "e:AnswerNoCommand" as AnswerNoCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":GameManager" as GameManager MODEL_COLOR +participant ":UniqueCardList" as UniqueCardList MODEL_COLOR +end box +[-> LogicManager : execute("no") +activate LogicManager +LogicManager -> MasterParser : parseCommand("no") +activate MasterParser +create AnswerNoCommand +MasterParser -> AnswerNoCommand +activate AnswerNoCommand +AnswerNoCommand --> MasterParser : e +deactivate AnswerNoCommand +MasterParser --> LogicManager : e +deactivate MasterParser +LogicManager -> AnswerNoCommand : execute() +activate AnswerNoCommand +AnswerNoCommand -> Model : answerNo() +activate Model +Model -> GameManager : answerNo() +activate GameManager +GameManager -> UniqueCardList : get(counter) +activate UniqueCardList +UniqueCardList -> GameManager : card +deactivate UniqueCardList +GameManager -> Model : card +deactivate GameManager +Model -> AnswerNoCommand : card +deactivate Model +AnswerNoCommand --> LogicManager : commandResult +deactivate AnswerNoCommand +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/AnswerYesSequenceDiagram.puml b/docs/diagrams/AnswerYesSequenceDiagram.puml new file mode 100644 index 00000000000..bcda8deb6d8 --- /dev/null +++ b/docs/diagrams/AnswerYesSequenceDiagram.puml @@ -0,0 +1,57 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":MasterParser" as MasterParser LOGIC_COLOR +participant "e:AnswerYesCommand" as AnswerYesCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":GameManager" as GameManager MODEL_COLOR +participant ":UniqueCardList" as UniqueCardList MODEL_COLOR +end box +[-> LogicManager : execute("yes") +activate LogicManager + +LogicManager -> MasterParser : parseCommand("yes") +activate MasterParser + +create AnswerYesCommand +MasterParser -> AnswerYesCommand +activate AnswerYesCommand + +AnswerYesCommand --> MasterParser : e +deactivate AnswerYesCommand + +MasterParser --> LogicManager : e +deactivate MasterParser + +LogicManager -> AnswerYesCommand : execute() +activate AnswerYesCommand + +AnswerYesCommand -> Model : answerYes() +activate Model + +Model -> GameManager : answerYes() +activate GameManager + +GameManager -> UniqueCardList : get(counter) +activate UniqueCardList + +UniqueCardList -> GameManager : card +deactivate UniqueCardList + +GameManager -> Model : card +deactivate GameManager + +Model -> AnswerYesCommand : card +deactivate Model + +AnswerYesCommand --> LogicManager : commandResult +deactivate AnswerYesCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ArchitectureDiagram.png b/docs/diagrams/ArchitectureDiagram.png new file mode 100644 index 00000000000..74a26277bee Binary files /dev/null and b/docs/diagrams/ArchitectureDiagram.png differ diff --git a/docs/diagrams/ArchitectureDiagram.puml b/docs/diagrams/ArchitectureDiagram.puml index d021b3992ed..6e4088c93cf 100644 --- a/docs/diagrams/ArchitectureDiagram.puml +++ b/docs/diagrams/ArchitectureDiagram.puml @@ -4,39 +4,40 @@ !include !include style.puml -Package " "<>{ +Package "Core"<>{ + Class Hidden #FFFFFF Class UI UI_COLOR Class Logic LOGIC_COLOR Class Storage STORAGE_COLOR Class Model MODEL_COLOR - Class Main MODEL_COLOR_T1 - Class Commons LOGIC_COLOR_T2 - Class "Log Center" as Logs UI_COLOR_T2 - Class Hidden #FFFFFF - Class HiddenUI #FFFFFF - Class HiddenModel #FFFFFF +} +scale 600 width -} Class "<$user>" as User MODEL_COLOR_T2 Class "<$documents>" as File UI_COLOR_T1 +Class Main MODEL_COLOR_T1 + +Class Commons LOGIC_COLOR_T2 +Class "LogsCenter" as Logs UI_COLOR_T2 +Class Index UI_COLOR_T2 +Class Messages UI_COLOR_T2 +Class Exceptions UI_COLOR_T2 +Main -right-> Core -HiddenUI -up[hidden]-> UI -HiddenModel -left[hidden]-> Model -Main -up-> HiddenUI -Main -left-> HiddenModel UI -> Logic UI -right-> Model Logic -> Storage Logic -down-> Model -Logs -right- Commons -Hidden .down.> Commons -Hidden .down.> Commons -Hidden .down.> Commons - Storage .right.>File User --> UI -Main --> Hidden + +Storage ..down[hidden]> Hidden +Commons -right-> Logs +Commons -down-> Index +Commons -down-> Messages +Commons -down-> Exceptions +Hidden .down.> Commons @enduml diff --git a/docs/diagrams/ArchitectureSequenceDiagram.png b/docs/diagrams/ArchitectureSequenceDiagram.png new file mode 100644 index 00000000000..2817b1ddd49 Binary files /dev/null and b/docs/diagrams/ArchitectureSequenceDiagram.png differ diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index d1e2ae93675..b4df12bb3f6 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -1,25 +1,27 @@ @startuml !include style.puml +scale 700 width + Actor User as user USER_COLOR Participant ":UI" as ui UI_COLOR Participant ":Logic" as logic LOGIC_COLOR Participant ":Model" as model MODEL_COLOR Participant ":Storage" as storage STORAGE_COLOR -user -[USER_COLOR]> ui : "delete 1" +user -[USER_COLOR]> ui : "remove 2" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("remove 2") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : removeDeck(d) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic deactivate model -logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) +logic -[LOGIC_COLOR]> storage : saveLibrary(library) activate storage STORAGE_COLOR storage -[STORAGE_COLOR]> storage : Save to file diff --git a/docs/diagrams/BetterModelClassDiagram.png b/docs/diagrams/BetterModelClassDiagram.png new file mode 100644 index 00000000000..1336542b3a2 Binary files /dev/null and b/docs/diagrams/BetterModelClassDiagram.png differ diff --git a/docs/diagrams/CommitActivityDiagram.png b/docs/diagrams/CommitActivityDiagram.png new file mode 100644 index 00000000000..834073ecce8 Binary files /dev/null and b/docs/diagrams/CommitActivityDiagram.png differ diff --git a/docs/diagrams/CreateSequenceDiagram.png b/docs/diagrams/CreateSequenceDiagram.png new file mode 100644 index 00000000000..76097c94c8d Binary files /dev/null and b/docs/diagrams/CreateSequenceDiagram.png differ diff --git a/docs/diagrams/CreateSequenceDiagram.puml b/docs/diagrams/CreateSequenceDiagram.puml new file mode 100644 index 00000000000..0adcdfd29d5 --- /dev/null +++ b/docs/diagrams/CreateSequenceDiagram.puml @@ -0,0 +1,76 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager " as LogicManager LOGIC_COLOR +participant ":MasterParser " as MasterParser LOGIC_COLOR +participant ":CreateDeckCommandParser " as CreateDeckCommandParser LOGIC_COLOR +participant "r:CreateDeckCommand " as CreateDeckCommand LOGIC_COLOR +participant ":CommandResult " as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":Library " as Library MODEL_COLOR +end box + +[-> LogicManager : execute("create Japanese") +activate LogicManager + +LogicManager -> MasterParser : parseCommand("create Japanese") +activate MasterParser + +create CreateDeckCommandParser +MasterParser -> CreateDeckCommandParser +activate CreateDeckCommandParser + +CreateDeckCommandParser --> MasterParser +deactivate CreateDeckCommandParser + +MasterParser -> CreateDeckCommandParser : parse("Japanese") +activate CreateDeckCommandParser + +create CreateDeckCommand +CreateDeckCommandParser -> CreateDeckCommand +activate CreateDeckCommand + +CreateDeckCommand --> CreateDeckCommandParser : r +deactivate CreateDeckCommand + +CreateDeckCommandParser --> MasterParser : r +deactivate CreateDeckCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +CreateDeckCommandParser -[hidden]-> MasterParser +destroy CreateDeckCommandParser + +MasterParser --> LogicManager : r +deactivate MasterParser + +LogicManager -> CreateDeckCommand : execute() +activate CreateDeckCommand + +CreateDeckCommand -> Model : createDeck("Japanese") +activate Model + +Model -> Library : createDeck("Japanese") +activate Library + +Library -> Model +deactivate Library + +Model --> CreateDeckCommand +deactivate Model + +create CommandResult +CreateDeckCommand -> CommandResult +activate CommandResult + +CommandResult --> CreateDeckCommand +deactivate CommandResult + +CreateDeckCommand --> LogicManager : result +deactivate CreateDeckCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.png b/docs/diagrams/DeleteSequenceDiagram.png new file mode 100644 index 00000000000..72d7696c42f Binary files /dev/null and b/docs/diagrams/DeleteSequenceDiagram.png differ diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..ebe435ac6cc 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -3,9 +3,9 @@ box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR -participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR -participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR +participant ":MasterParser" as MasterParser LOGIC_COLOR +participant ":RemoveDeckCommandParser" as RemoveDeckCommandParser LOGIC_COLOR +participant "r:RemoveDeckCommand" as RemoveDeckCommand LOGIC_COLOR participant ":CommandResult" as CommandResult LOGIC_COLOR end box @@ -13,56 +13,56 @@ box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR end box -[-> LogicManager : execute("delete 1") +[-> LogicManager : execute("remove 2") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") -activate AddressBookParser +LogicManager -> MasterParser : parseCommand("remove 2") +activate MasterParser -create DeleteCommandParser -AddressBookParser -> DeleteCommandParser -activate DeleteCommandParser +create RemoveDeckCommandParser +MasterParser -> RemoveDeckCommandParser +activate RemoveDeckCommandParser -DeleteCommandParser --> AddressBookParser -deactivate DeleteCommandParser +RemoveDeckCommandParser --> MasterParser +deactivate RemoveDeckCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") -activate DeleteCommandParser +MasterParser -> RemoveDeckCommandParser : parse("2") +activate RemoveDeckCommandParser -create DeleteCommand -DeleteCommandParser -> DeleteCommand -activate DeleteCommand +create RemoveDeckCommand +RemoveDeckCommandParser -> RemoveDeckCommand +activate RemoveDeckCommand -DeleteCommand --> DeleteCommandParser : d -deactivate DeleteCommand +RemoveDeckCommand --> RemoveDeckCommandParser : r +deactivate RemoveDeckCommand -DeleteCommandParser --> AddressBookParser : d -deactivate DeleteCommandParser +RemoveDeckCommandParser --> MasterParser : r +deactivate RemoveDeckCommandParser 'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser -destroy DeleteCommandParser +RemoveDeckCommandParser -[hidden]-> MasterParser +destroy RemoveDeckCommandParser -AddressBookParser --> LogicManager : d -deactivate AddressBookParser +MasterParser --> LogicManager : r +deactivate MasterParser -LogicManager -> DeleteCommand : execute() -activate DeleteCommand +LogicManager -> RemoveDeckCommand : execute() +activate RemoveDeckCommand -DeleteCommand -> Model : deletePerson(1) +RemoveDeckCommand -> Model : removeDeck(2) activate Model -Model --> DeleteCommand +Model --> RemoveDeckCommand deactivate Model create CommandResult -DeleteCommand -> CommandResult +RemoveDeckCommand -> CommandResult activate CommandResult -CommandResult --> DeleteCommand +CommandResult --> RemoveDeckCommand deactivate CommandResult -DeleteCommand --> LogicManager : result -deactivate DeleteCommand +RemoveDeckCommand --> LogicManager : result +deactivate RemoveDeckCommand [<--LogicManager deactivate LogicManager diff --git a/docs/diagrams/EditCardActivityDiagram.puml b/docs/diagrams/EditCardActivityDiagram.puml new file mode 100644 index 00000000000..7be0dc7d2c7 --- /dev/null +++ b/docs/diagrams/EditCardActivityDiagram.puml @@ -0,0 +1,18 @@ +@startuml +start + :enter valid Edit command; + + if () then ([has new front and back]) + : create new Card with new front and back; + else ([else]) + if () then ([has new front only]) + : retrieve old back; + : create new Card with new front and old back; + else ([has new back only]) + : retrieve old front; + : create new Card with old front and new back; + endif + endif + : replace Card to be edited with new Card; + stop + @enduml diff --git a/docs/diagrams/EditSequenceDiagram.png b/docs/diagrams/EditSequenceDiagram.png new file mode 100644 index 00000000000..a355b9fa1b6 Binary files /dev/null and b/docs/diagrams/EditSequenceDiagram.png differ diff --git a/docs/diagrams/EditSequenceDiagram.puml b/docs/diagrams/EditSequenceDiagram.puml new file mode 100644 index 00000000000..92244c0934b --- /dev/null +++ b/docs/diagrams/EditSequenceDiagram.puml @@ -0,0 +1,71 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":MasterParser" as MasterParser LOGIC_COLOR +participant ":EditCardCommandParser" as EditCardCommandParser LOGIC_COLOR +participant "e:EditCardCommand" as EditCardCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":Deck" as Deck MODEL_COLOR +participant ":UniqueCardList" as UniqueCardList MODEL_COLOR +end box +[-> LogicManager : execute("edit 1 fr:bk") +activate LogicManager + +LogicManager -> MasterParser : parseCommand("edit 1 fr:bk") +activate MasterParser + +create EditCardCommandParser +MasterParser -> EditCardCommandParser +activate EditCardCommandParser + +EditCardCommandParser --> MasterParser +deactivate EditCardCommandParser + +MasterParser -> EditCardCommandParser : parse("1 fr:bk") +activate EditCardCommandParser + +create EditCardCommand +EditCardCommandParser -> EditCardCommand +activate EditCardCommand + +EditCardCommand --> EditCardCommandParser : e +deactivate EditCardCommand + +EditCardCommandParser --> MasterParser : e +deactivate EditCardCommandParser + +MasterParser --> LogicManager : e +deactivate MasterParser + +LogicManager -> EditCardCommand : execute() +activate EditCardCommand + +EditCardCommand -> Model : replaceCard(old, new) +activate Model + +Model -> Deck : replace(old, new) +activate Deck + +Deck -> UniqueCardList: replace(old, new) +activate UniqueCardList + +UniqueCardList --> Deck : +deactivate UniqueCardList + +Deck --> Model +deactivate Deck + +Model --> EditCardCommand +deactivate Model + +EditCardCommand --> LogicManager : commandResult +deactivate EditCardCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/FlipSequenceDiagram.puml b/docs/diagrams/FlipSequenceDiagram.puml new file mode 100644 index 00000000000..ae8056f9001 --- /dev/null +++ b/docs/diagrams/FlipSequenceDiagram.puml @@ -0,0 +1,64 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":MasterParser" as MasterParser LOGIC_COLOR +participant "e:FlipCommand" as FLipCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":GameManager" as GameManager MODEL_COLOR +participant ":UniqueCardList" as UniqueCardList MODEL_COLOR +participant ":Card" as Card MODEL_COLOR +end box +[-> LogicManager : execute("flip") +activate LogicManager + +LogicManager -> MasterParser : parseCommand("flip") +activate MasterParser + +create FLipCommand +MasterParser -> FLipCommand +activate FLipCommand + +FLipCommand --> MasterParser : e +deactivate FLipCommand + +MasterParser --> LogicManager : e +deactivate MasterParser + +LogicManager -> FLipCommand : execute() +activate FLipCommand + +FLipCommand -> Model : flip() +activate Model + +Model -> GameManager : flip() +activate GameManager + +GameManager -> UniqueCardList : get(counter) +activate UniqueCardList + +UniqueCardList -> Card : getBackFace() +activate Card + +Card -> UniqueCardList : backFace +deactivate Card + +UniqueCardList -> GameManager : backFace +deactivate UniqueCardList + +GameManager -> Model : backFace +deactivate GameManager + +Model -> FLipCommand : backFace +deactivate Model + +FLipCommand --> LogicManager : commandResult +deactivate FLipCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/LogicClassDiagram.png b/docs/diagrams/LogicClassDiagram.png new file mode 100644 index 00000000000..f1031a8f037 Binary files /dev/null and b/docs/diagrams/LogicClassDiagram.png differ diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml index 016ef33e2e2..640093c09de 100644 --- a/docs/diagrams/LogicClassDiagram.puml +++ b/docs/diagrams/LogicClassDiagram.puml @@ -8,17 +8,13 @@ package Logic { package Parser { Interface Parser <> -Class AddressBookParser -Class XYZCommandParser -Class CliSyntax +Class MasterParser +Class *CommandParser Class ParserUtil -Class ArgumentMultimap -Class ArgumentTokenizer -Class Prefix } package Command { -Class XYZCommand +Class *Command Class CommandResult Class "{abstract}\nCommand" as Command } @@ -35,25 +31,18 @@ Class HiddenOutside #FFFFFF HiddenOutside ..> Logic LogicManager .up.|> Logic -LogicManager -->"1" AddressBookParser -AddressBookParser .left.> XYZCommandParser: creates > - -XYZCommandParser ..> XYZCommand : creates > -XYZCommandParser ..|> Parser -XYZCommandParser ..> ArgumentMultimap -XYZCommandParser ..> ArgumentTokenizer -ArgumentTokenizer .left.> ArgumentMultimap -XYZCommandParser ..> CliSyntax -CliSyntax ..> Prefix -XYZCommandParser ..> ParserUtil -ParserUtil .down.> Prefix -ArgumentTokenizer .down.> Prefix -XYZCommand -up-|> Command +LogicManager -->"1" MasterParser +MasterParser .left.> "*CommandParser": creates > + +"*CommandParser" ..> "*Command" : creates > +"*CommandParser" ..|> Parser +"*CommandParser" ..> ParserUtil +"*Command" -up-|> Command LogicManager .left.> Command : executes > LogicManager --> Model Command .right.> Model -note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc +note right of "*Command": ~*Command: AddCardCommand, \nRemoveDeckCommand, etc. Logic ..> CommandResult LogicManager .down.> CommandResult diff --git a/docs/diagrams/ModelClassDeckDiagram.png b/docs/diagrams/ModelClassDeckDiagram.png new file mode 100644 index 00000000000..497bfd5bc6b Binary files /dev/null and b/docs/diagrams/ModelClassDeckDiagram.png differ diff --git a/docs/diagrams/ModelClassDeckDiagram.puml b/docs/diagrams/ModelClassDeckDiagram.puml new file mode 100644 index 00000000000..458830ffbdd --- /dev/null +++ b/docs/diagrams/ModelClassDeckDiagram.puml @@ -0,0 +1,31 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +Package Deck <>{ +Class Deck +Class Name +Class UniqueDeckList +Package Card { +Class UniqueCardList +Class Card +Class FrontFace +class BackFace +} +} + + + +Class HiddenOutside #FFFFFF +HiddenOutside --> UniqueDeckList + +UniqueDeckList o--> "*" Deck +Deck *--> Name +Deck *--> "1" UniqueCardList +UniqueCardList o--> "*" Card +Card *--> FrontFace +Card *--> BackFace + +@enduml diff --git a/docs/diagrams/ModelClassDiagram.png b/docs/diagrams/ModelClassDiagram.png new file mode 100644 index 00000000000..af30efc7d50 Binary files /dev/null and b/docs/diagrams/ModelClassDiagram.png differ diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index e85a00d4107..644c0dfbf34 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -5,52 +5,47 @@ skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR Package Model <>{ -Interface ReadOnlyAddressBook <> +Interface ReadOnlyLibrary <> Interface Model <> Interface ObservableList <> -Class AddressBook -Class ReadOnlyAddressBook +Class Library +Class ReadOnlyLibrary Class Model Class ModelManager Class UserPrefs Class ReadOnlyUserPrefs +Class GameManager +Class Mode +Class View -Package Person { -Class Person -Class Address -Class Email -Class Name -Class Phone -Class UniquePersonList -} +Class Deck +Class UniqueDeckList +Class UniqueCardList +Class Card -Package Tag { -Class Tag -} -} Class HiddenOutside #FFFFFF -HiddenOutside ..> Model -AddressBook .up.|> ReadOnlyAddressBook + +Library .up.|> ReadOnlyLibrary ModelManager .up.|> Model Model .right.> ObservableList -ModelManager o--> "1" AddressBook +ModelManager o--> "1" Library +ModelManager o--> "0..1" GameManager +ModelManager o--> "0..1" Deck +ModelManager o--> "0..1" Card +ModelManager o--> "1" Mode +ModelManager o--> "1" View ModelManager o-left-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList -UniquePersonList o--> "*" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag -Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +Library *--> "1" UniqueDeckList +GameManager *--> "1" UniqueCardList +UniqueDeckList o--> "*" Deck +Deck *--> "1" UniqueCardList +UniqueCardList o--> "*" Card + -ModelManager -->"1" Person : filtered list @enduml diff --git a/docs/diagrams/PlaySequenceDiagram.puml b/docs/diagrams/PlaySequenceDiagram.puml new file mode 100644 index 00000000000..d8d762bc371 --- /dev/null +++ b/docs/diagrams/PlaySequenceDiagram.puml @@ -0,0 +1,79 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":MasterParser" as MasterParser LOGIC_COLOR +participant ":PlayCommandParser" as PlayCommandParser LOGIC_COLOR +participant "e:PlayCommand" as PlayCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":Library" as Library MODEL_COLOR +participant ":GameManager" as GameManager MODEL_COLOR +participant ":Deck" as Deck MODEL_COLOR +end box +[-> LogicManager : execute("play 1") +activate LogicManager + +LogicManager -> MasterParser : parseCommand("play 1") +activate MasterParser + +create PlayCommandParser +MasterParser -> PlayCommandParser +activate PlayCommandParser + +PlayCommandParser --> MasterParser +deactivate PlayCommandParser + +MasterParser -> PlayCommandParser : parse("1") +activate PlayCommandParser + +create PlayCommand +PlayCommandParser -> PlayCommand +activate PlayCommand + +PlayCommand --> PlayCommandParser : e +deactivate PlayCommand + +PlayCommandParser --> MasterParser : e +deactivate PlayCommandParser + +MasterParser --> LogicManager : e +deactivate MasterParser + +LogicManager -> PlayCommand : execute() +activate PlayCommand + +PlayCommand -> Model : play(1) +activate Model + +Model -> Library : getDeck(1) +activate Library + +Library -> Model : deck +deactivate Library + +create GameManager +Model -> GameManager : deck +activate GameManager + +GameManager -> Deck : asUnmodifiableObservableList() +activate Deck + +Deck -> GameManager : cards +deactivate Deck + +GameManager -> Model : e +deactivate GameManager + +Model --> PlayCommand +deactivate Model + +PlayCommand --> LogicManager : commandResult +deactivate PlayCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/SelectDeckSequenceDiagram.png b/docs/diagrams/SelectDeckSequenceDiagram.png new file mode 100644 index 00000000000..39ebfb80178 Binary files /dev/null and b/docs/diagrams/SelectDeckSequenceDiagram.png differ diff --git a/docs/diagrams/SelectDeckSequenceDiagram.puml b/docs/diagrams/SelectDeckSequenceDiagram.puml new file mode 100644 index 00000000000..95e06e24d8c --- /dev/null +++ b/docs/diagrams/SelectDeckSequenceDiagram.puml @@ -0,0 +1,108 @@ +@startuml +!include style.puml + +actor User as user + +box UI UI_COLOR_T1 +participant ":DeckListPanel" as DeckListPanel UI_COLOR +participant ":CardListPanel" as CardListPanel UI_COLOR +participant ":ResultDisplay" as ResultDisplay UI_COLOR +participant ":CommandBox" as CommandBox UI_COLOR +end box + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":MasterParser" as MasterParser LOGIC_COLOR +participant ":SelectDeckCommandParser" as SelectDeckCommandParser LOGIC_COLOR +participant "e:SelectDeckCommand" as SelectDeckCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":Library" as Library MODEL_COLOR +participant ":UniqueDeckList" as UniqueDeckList MODEL_COLOR + +end box + +user -> CommandBox : type: "select 1" +activate CommandBox + +CommandBox -> LogicManager : execute("select 1") +deactivate CommandBox +activate LogicManager + + +LogicManager -> MasterParser : parseCommand("select 1") +activate MasterParser + +create SelectDeckCommandParser +MasterParser -> SelectDeckCommandParser +activate SelectDeckCommandParser + +SelectDeckCommandParser --> MasterParser +deactivate SelectDeckCommandParser + +MasterParser -> SelectDeckCommandParser : parse("1") +activate SelectDeckCommandParser + +create SelectDeckCommand +SelectDeckCommandParser -> SelectDeckCommand +activate SelectDeckCommand + +SelectDeckCommand --> SelectDeckCommandParser : e +deactivate SelectDeckCommand + +SelectDeckCommandParser --> MasterParser : e +deactivate SelectDeckCommandParser + +MasterParser --> LogicManager : e +deactivate MasterParser + +LogicManager -> SelectDeckCommand : execute() +activate SelectDeckCommand + + +SelectDeckCommand -> Model : selectDeck(targetIdx) +activate Model + +Model -> Library : getDeck(targetIdx) +activate Library + +Library -> UniqueDeckList: get(targetIdx) +activate UniqueDeckList + + +UniqueDeckList --> Library : deck +deactivate UniqueDeckList + +Library --> Model : deck +deactivate Library + +Model --> LogicManager: setSelectedDeck(deck) + +Model --> SelectDeckCommand +deactivate Model + +SelectDeckCommand --> LogicManager : commandResult +deactivate SelectDeckCommand + +LogicManager --> DeckListPanel : setSelectedDeck(deck) +activate DeckListPanel + +LogicManager --> CardListPanel : setSelectedDeck(deck) +activate CardListPanel + +LogicManager --> ResultDisplay: commandResult +deactivate LogicManager +activate ResultDisplay + +DeckListPanel --> user: clearAndSelect(deck) +deactivate DeckListPanel + +CardListPanel --> user: showCardList(deck) +deactivate CardListPanel + +ResultDisplay --> user: setFeedbackToUser(commandResult) +deactivate ResultDisplay + +@enduml diff --git a/docs/diagrams/StorageClassDiagram.png b/docs/diagrams/StorageClassDiagram.png new file mode 100644 index 00000000000..84e99fe4a67 Binary files /dev/null and b/docs/diagrams/StorageClassDiagram.png differ diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 6adb2e156bf..8c9cad71567 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -6,19 +6,19 @@ skinparam classBackgroundColor STORAGE_COLOR Interface Storage <> Interface UserPrefsStorage <> -Interface AddressBookStorage <> +Interface LibraryStorage <> Class StorageManager Class JsonUserPrefsStorage -Class JsonAddressBookStorage +Class JsonLibraryStorage StorageManager .left.|> Storage StorageManager o-right-> UserPrefsStorage -StorageManager o--> AddressBookStorage +StorageManager o--> LibraryStorage JsonUserPrefsStorage .left.|> UserPrefsStorage -JsonAddressBookStorage .left.|> AddressBookStorage -JsonAddressBookStorage .down.> JsonSerializableAddressBookStorage -JsonSerializableAddressBookStorage .right.> JsonSerializablePerson -JsonSerializablePerson .right.> JsonAdaptedTag +JsonLibraryStorage .left.|> LibraryStorage +JsonLibraryStorage .down.> JsonSerializableLibraryStorage +JsonSerializableLibraryStorage .right.> JsonAdaptedDeck +JsonAdaptedDeck .right.> JsonAdaptedCard @enduml diff --git a/docs/diagrams/UiClassDiagram.png b/docs/diagrams/UiClassDiagram.png new file mode 100644 index 00000000000..2716ce06e7b Binary files /dev/null and b/docs/diagrams/UiClassDiagram.png differ diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 92746f9fcf7..2382d2884c6 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -4,6 +4,7 @@ skinparam arrowThickness 1.1 skinparam arrowColor UI_COLOR_T4 skinparam classBackgroundColor UI_COLOR + package UI <>{ Interface Ui <> Class "{abstract}\nUiPart" as UiPart @@ -11,10 +12,13 @@ Class UiManager Class MainWindow Class HelpWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard +Class DeckListPanel +Class DeckCard Class StatusBarFooter Class CommandBox +Class CardListPanel +Class PlayPanel +Class StatisticsPopUp } package Model <> { @@ -30,31 +34,39 @@ HiddenOutside ..> Ui UiManager .left.|> Ui UiManager -down-> MainWindow -MainWindow --> HelpWindow +MainWindow -up-> HelpWindow +MainWindow -up-> StatisticsPopUp MainWindow *-down-> CommandBox MainWindow *-down-> ResultDisplay -MainWindow *-down-> PersonListPanel +MainWindow -down-> CardListPanel +MainWindow -down-> PlayPanel +MainWindow *-down-> DeckListPanel MainWindow *-down-> StatusBarFooter -PersonListPanel -down-> PersonCard +DeckListPanel -down-> DeckCard MainWindow -left-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart +CardListPanel --|> UiPart +PlayPanel --|> UiPart +DeckListPanel --|> UiPart +DeckCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow -down-|> UiPart +StatisticsPopUp -down-|> UiPart -PersonCard ..> Model +DeckCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic -PersonListPanel -[hidden]left- HelpWindow -HelpWindow -[hidden]left- CommandBox +DeckListPanel -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay -ResultDisplay -[hidden]left- StatusBarFooter +ResultDisplay -[hidden]left- CardListPanel +CardListPanel -[hidden]left- PlayPanel +PlayPanel -[hidden]left- StatusBarFooter +StatisticsPopUp -[hidden]left- HelpWindow MainWindow -[hidden]-|> UiPart @enduml diff --git a/docs/diagrams/UndoRedoState0.png b/docs/diagrams/UndoRedoState0.png new file mode 100644 index 00000000000..41400ea3338 Binary files /dev/null and b/docs/diagrams/UndoRedoState0.png differ diff --git a/docs/diagrams/UndoRedoState1.png b/docs/diagrams/UndoRedoState1.png new file mode 100644 index 00000000000..0fecc603b40 Binary files /dev/null and b/docs/diagrams/UndoRedoState1.png differ diff --git a/docs/diagrams/UndoRedoState2.png b/docs/diagrams/UndoRedoState2.png new file mode 100644 index 00000000000..39407ff5754 Binary files /dev/null and b/docs/diagrams/UndoRedoState2.png differ diff --git a/docs/diagrams/UndoRedoState3.png b/docs/diagrams/UndoRedoState3.png new file mode 100644 index 00000000000..6e399839503 Binary files /dev/null and b/docs/diagrams/UndoRedoState3.png differ diff --git a/docs/diagrams/UndoRedoState4.png b/docs/diagrams/UndoRedoState4.png new file mode 100644 index 00000000000..3e2bbc1c122 Binary files /dev/null and b/docs/diagrams/UndoRedoState4.png differ diff --git a/docs/diagrams/UndoRedoState5.png b/docs/diagrams/UndoRedoState5.png new file mode 100644 index 00000000000..b51c415a1b0 Binary files /dev/null and b/docs/diagrams/UndoRedoState5.png differ diff --git a/docs/diagrams/UndoSequenceDiagram.png b/docs/diagrams/UndoSequenceDiagram.png new file mode 100644 index 00000000000..25fe4b765c6 Binary files /dev/null and b/docs/diagrams/UndoSequenceDiagram.png differ diff --git a/docs/diagrams/plantuml/AbeforeC.png b/docs/diagrams/plantuml/AbeforeC.png new file mode 100644 index 00000000000..b12d3f370ef Binary files /dev/null and b/docs/diagrams/plantuml/AbeforeC.png differ diff --git a/docs/diagrams/plantuml/AllDown.png b/docs/diagrams/plantuml/AllDown.png new file mode 100644 index 00000000000..494bb76d4b0 Binary files /dev/null and b/docs/diagrams/plantuml/AllDown.png differ diff --git a/docs/diagrams/plantuml/ArrowLength.png b/docs/diagrams/plantuml/ArrowLength.png new file mode 100644 index 00000000000..9f702ad8f71 Binary files /dev/null and b/docs/diagrams/plantuml/ArrowLength.png differ diff --git a/docs/diagrams/plantuml/CbeforeA.png b/docs/diagrams/plantuml/CbeforeA.png new file mode 100644 index 00000000000..518e444915c Binary files /dev/null and b/docs/diagrams/plantuml/CbeforeA.png differ diff --git a/docs/diagrams/plantuml/HiddenArrows.png b/docs/diagrams/plantuml/HiddenArrows.png new file mode 100644 index 00000000000..6e67057000b Binary files /dev/null and b/docs/diagrams/plantuml/HiddenArrows.png differ diff --git a/docs/diagrams/plantuml/PackagesAndConsistency.png b/docs/diagrams/plantuml/PackagesAndConsistency.png new file mode 100644 index 00000000000..8a707748d78 Binary files /dev/null and b/docs/diagrams/plantuml/PackagesAndConsistency.png differ diff --git a/docs/diagrams/plantuml/UpAndDown.png b/docs/diagrams/plantuml/UpAndDown.png new file mode 100644 index 00000000000..749e3bed8bf Binary files /dev/null and b/docs/diagrams/plantuml/UpAndDown.png differ diff --git a/docs/images/AddSequenceDiagram.png b/docs/images/AddSequenceDiagram.png new file mode 100644 index 00000000000..3a651385fbd Binary files /dev/null and b/docs/images/AddSequenceDiagram.png differ diff --git a/docs/images/AnswerNoSequenceDiagram.png b/docs/images/AnswerNoSequenceDiagram.png new file mode 100644 index 00000000000..6891232bbd2 Binary files /dev/null and b/docs/images/AnswerNoSequenceDiagram.png differ diff --git a/docs/images/AnswerYesSequenceDiagram.png b/docs/images/AnswerYesSequenceDiagram.png new file mode 100644 index 00000000000..8f3b09a81bc Binary files /dev/null and b/docs/images/AnswerYesSequenceDiagram.png differ diff --git a/docs/images/ArchitectureDiagram.png b/docs/images/ArchitectureDiagram.png index aa2d337d932..286e6a33bb8 100644 Binary files a/docs/images/ArchitectureDiagram.png and b/docs/images/ArchitectureDiagram.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index aa198138f8f..d090165e6b6 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/CreateSequenceDiagram.png b/docs/images/CreateSequenceDiagram.png new file mode 100644 index 00000000000..76097c94c8d Binary files /dev/null and b/docs/images/CreateSequenceDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..2aaadea8094 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/EditCardActivityDiagram.png b/docs/images/EditCardActivityDiagram.png new file mode 100644 index 00000000000..0da94e18fbc Binary files /dev/null and b/docs/images/EditCardActivityDiagram.png differ diff --git a/docs/images/EditSequenceDiagram.png b/docs/images/EditSequenceDiagram.png new file mode 100644 index 00000000000..10129a0bf63 Binary files /dev/null and b/docs/images/EditSequenceDiagram.png differ diff --git a/docs/images/FlipSequenceDiagram.png b/docs/images/FlipSequenceDiagram.png new file mode 100644 index 00000000000..797d3fe259b Binary files /dev/null and b/docs/images/FlipSequenceDiagram.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index b9e853cef12..cb40bde3c80 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/ModelClassDeckDiagram.png b/docs/images/ModelClassDeckDiagram.png new file mode 100644 index 00000000000..497bfd5bc6b Binary files /dev/null and b/docs/images/ModelClassDeckDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 280064118cf..af30efc7d50 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/PlaySequenceDiagram.png b/docs/images/PlaySequenceDiagram.png new file mode 100644 index 00000000000..09e3a487b43 Binary files /dev/null and b/docs/images/PlaySequenceDiagram.png differ diff --git a/docs/images/SelectDeckSequenceDiagram.png b/docs/images/SelectDeckSequenceDiagram.png new file mode 100644 index 00000000000..39ebfb80178 Binary files /dev/null and b/docs/images/SelectDeckSequenceDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index d87c1216820..84e99fe4a67 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..325706ffbab 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/Ui2.png b/docs/images/Ui2.png new file mode 100644 index 00000000000..38df6d75022 Binary files /dev/null and b/docs/images/Ui2.png differ diff --git a/docs/images/Ui3.png b/docs/images/Ui3.png new file mode 100644 index 00000000000..9de8cba977a Binary files /dev/null and b/docs/images/Ui3.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 7b4b3dbea45..2716ce06e7b 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/add.png b/docs/images/add.png new file mode 100644 index 00000000000..e52d6f2ea32 Binary files /dev/null and b/docs/images/add.png differ diff --git a/docs/images/amrl.png b/docs/images/amrl.png new file mode 100644 index 00000000000..cd8ac1e770b Binary files /dev/null and b/docs/images/amrl.png differ diff --git a/docs/images/create1.png b/docs/images/create1.png new file mode 100644 index 00000000000..569c55fd253 Binary files /dev/null and b/docs/images/create1.png differ diff --git a/docs/images/create2.png b/docs/images/create2.png new file mode 100644 index 00000000000..5a567fbe14f Binary files /dev/null and b/docs/images/create2.png differ diff --git a/docs/images/delete.png b/docs/images/delete.png new file mode 100644 index 00000000000..ac1e5988622 Binary files /dev/null and b/docs/images/delete.png differ diff --git a/docs/images/edit.png b/docs/images/edit.png new file mode 100644 index 00000000000..c54d2c0b2c5 Binary files /dev/null and b/docs/images/edit.png differ diff --git a/docs/images/flashspeed_logo.png b/docs/images/flashspeed_logo.png new file mode 100644 index 00000000000..f7416eaa45c Binary files /dev/null and b/docs/images/flashspeed_logo.png differ diff --git a/docs/images/flip.png b/docs/images/flip.png new file mode 100644 index 00000000000..012253dd13e Binary files /dev/null and b/docs/images/flip.png differ diff --git a/docs/images/help.png b/docs/images/help.png new file mode 100644 index 00000000000..ba2cd163c7d Binary files /dev/null and b/docs/images/help.png differ diff --git a/docs/images/kschiew.png b/docs/images/kschiew.png new file mode 100644 index 00000000000..81be18531eb Binary files /dev/null and b/docs/images/kschiew.png differ diff --git a/docs/images/lacedaemon98.png b/docs/images/lacedaemon98.png new file mode 100644 index 00000000000..b40d0d42e31 Binary files /dev/null and b/docs/images/lacedaemon98.png differ diff --git a/docs/images/library.png b/docs/images/library.png new file mode 100644 index 00000000000..8b105584486 Binary files /dev/null and b/docs/images/library.png differ diff --git a/docs/images/logos.jpg b/docs/images/logos.jpg new file mode 100644 index 00000000000..4143c1031b3 Binary files /dev/null and b/docs/images/logos.jpg differ diff --git a/docs/images/ncslzh.png b/docs/images/ncslzh.png new file mode 100644 index 00000000000..0d601f1cd7f Binary files /dev/null and b/docs/images/ncslzh.png differ diff --git a/docs/images/olixino.png b/docs/images/olixino.png new file mode 100644 index 00000000000..0aafa2e939b Binary files /dev/null and b/docs/images/olixino.png differ diff --git a/docs/images/remove.png b/docs/images/remove.png new file mode 100644 index 00000000000..937f74757b2 Binary files /dev/null and b/docs/images/remove.png differ diff --git a/docs/images/rename.png b/docs/images/rename.png new file mode 100644 index 00000000000..ea9cea55023 Binary files /dev/null and b/docs/images/rename.png differ diff --git a/docs/images/reset.png b/docs/images/reset.png new file mode 100644 index 00000000000..c17ebd6c688 Binary files /dev/null and b/docs/images/reset.png differ diff --git a/docs/images/stop.png b/docs/images/stop.png new file mode 100644 index 00000000000..f7bdeab6255 Binary files /dev/null and b/docs/images/stop.png differ diff --git a/docs/images/yes.png b/docs/images/yes.png new file mode 100644 index 00000000000..42b1a9b38bf Binary files /dev/null and b/docs/images/yes.png differ diff --git a/docs/team/amrl.adoc b/docs/team/amrl.adoc new file mode 100644 index 00000000000..c4ddbe3398c --- /dev/null +++ b/docs/team/amrl.adoc @@ -0,0 +1,95 @@ += Amirul Ardy - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: FlashSpeed + +== Overview + +FlashSpeed is a text-based flashcard application which enables users to store virtual flashcards, categorize them within +decks, and launch review sessions of cards within those decks. It is designed for university students who are learning a +foreign language or reading a language module. + +It was developed as a team project for the CS2103T Software Engineering module in the National University of Singapore +(NUS). FlashSpeed was built on top of the AddressBook-Level3 project created by the https://se-education.org[SE-EDU initiative]. +FlashSpeed is written in Java and has a GUI created using JavaFX. Users interact with FlashSpeed mainly using textual commands. + +== Summary of contributions + +My roles in this project include being the technical leader and in-charge of the User Guide. +As technical leader my responsibilities include ensuring the establishment of the week's tasks, adherence to the proper development workflow, and quality checks on pull requests. + +* *Major enhancement*: Added the *ability to edit cards and edit using shortened commands.* +** What it does: Allows the user to perform the editing of card values using any one of three different command formats, where two of the formats are shortened forms (i.e. edit INDEX FRONT:BACK, edit INDEX :BACK, edit INDEX FRONT:). +** Justification: This feature improves the product significantly because a user will be able to perform the editing of only the values they want to change, rather than having to retype the same value. This provides convenience and overall efficiency to the user. This functionality had the possibility to require a change in the entire logical chain but I decided that a reuse of existing functionality (e.g. add, delete, etc. to perform a Card replacement) would both reduce the possibility of logical bugs and code rewrite. +** Highlights: This is the only command in the application which can take in multiple formats. The implementation of this feature required design considerations across the entire model of the application (e.g. overwrite values in the Card or do a full replacement with a new Card, where should the new Card interface with the Deck to retrieve the unchanged values if any, etc.) The edit functionality fulfilled its extended goals while being able to be treated as another standard command by the user and other developers. + +* *Minor enhancement*: Added the implementations for the functional units of the application, i.e. Cards, Decks, and its UniqueCardLists. +** Highlights: These are the abstractions of data which makes FlashSpeed a flashcard application. They are built in such a way that developers of the higher level logic and model management can treat them as total black boxes. These are also the items which users will be interacting with mostly. + +* *Code contributed*: [https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=&sort=groupTitle&sortWithin=title&since=2020-02-14&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=amrl&tabRepo=AY1920S2-CS2103T-W17-1%2Fmain%5Bmaster%5D[Functional code]] [https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=&sort=groupTitle&sortWithin=title&since=2020-02-14&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=amrl&tabRepo=AY1920S2-CS2103T-W17-1%2Fmain%5Bmaster%5D[Test code]] +** > 1.2k LoC +** > 40 Pull Requests: https://github.com/AY1920S2-CS2103T-W17-1/main/pulls?q=author%3Aamrl[PRs] + +* *Documentation contributed*: +** User Guide: +*** In-charge of document +*** Wrote or edited all sections either fully or partially +*** Reformatted the structure and layout for ease of readability and navigability (e.g. topic headings, section spacing, etc.) +*** Maintenance of document to be up-to-date with current application version +*** Added all tips and notes, command summary table, etc. + +** Developer Guide: +*** Wrote the `Design: Architecture` section and `Implementation: Editing a Card` section in the Developer Guide +*** UML diagrams: +https://github.com/AY1920S2-CS2103T-W17-1/main/blob/master/docs/images/ArchitectureDiagram.png[Architecture], +https://github.com/AY1920S2-CS2103T-W17-1/main/blob/master/docs/images/LogicClassDiagram.png[Logic Class], +https://github.com/AY1920S2-CS2103T-W17-1/main/blob/master/docs/images/ArchitectureSequenceDiagram.png[Architecture Sequence], +https://github.com/AY1920S2-CS2103T-W17-1/main/blob/master/docs/images/DeleteSequenceDiagram.png[Remove Sequence], +https://github.com/AY1920S2-CS2103T-W17-1/main/blob/master/docs/images/EditCardActivityDiagram.png[Edit Card Activity], +https://github.com/AY1920S2-CS2103T-W17-1/main/blob/master/docs/images/EditSequenceDiagram.png[Edit Sequence] + +** Others: +*** Wrote the content in the README and site home page which includes our product description, marketing blurb, and target users + +* *Other contributions*: + +** Project management: +*** Managed all releases `v1.1` - `v1.4` (6 releases) on GitHub +** Enhancements to existing features: +*** Added functionality and methods in ModelManager which uses our Cards and Decks as functional units +** Review/mentoring contributions: +*** PRs reviewed: https://github.com/AY1920S2-CS2103T-W17-1/main/pull/81[#81] https://github.com/AY1920S2-CS2103T-W17-1/main/pull/84[#84] https://github.com/AY1920S2-CS2103T-W17-1/main/pull/95[#95] etc. +*** Bug fixes in team members' sections: https://github.com/AY1920S2-CS2103T-W17-1/main/pull/116[#116] https://github.com/AY1920S2-CS2103T-W17-1/main/pull/129[#129] https://github.com/AY1920S2-CS2103T-W17-1/main/pull/131[#131] https://github.com/AY1920S2-CS2103T-W17-1/main/pull/139[#139] https://github.com/AY1920S2-CS2103T-W17-1/main/pull/222[#222] etc. +** Changes: +*** Morphed product site to be in line with our FlashSpeed product and branding +*** Morphed codebase to remove and replace all AddressBook-Level3 references to become FlashSpeed +** Tools: +*** Set-up of Netlify +*** Set-up and maintenance of GitHub issue tracker +*** Maintenance of Travis according to project workflow + +== 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] +--- +include::../UserGuide.adoc[tag=library-view] +--- +include::../UserGuide.adoc[tag=select-deck] +--- +include::../UserGuide.adoc[tag=command-table] + +== 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=design-arch] +--- +include::../DeveloperGuide.adoc[tag=editcard] diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc index f39e76e49b2..9a72f1f2064 100644 --- a/docs/team/johndoe.adoc +++ b/docs/team/johndoe.adoc @@ -50,9 +50,9 @@ _{you can add/remove categories in the list above}_ |_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ |=== -include::../UserGuide.adoc[tag=delete] +// include::../UserGuide.adoc[tag=delete] -include::../UserGuide.adoc[tag=dataencryption] +// include::../UserGuide.adoc[tag=dataencryption] == Contributions to the Developer Guide @@ -60,9 +60,9 @@ include::../UserGuide.adoc[tag=dataencryption] |_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ |=== -include::../DeveloperGuide.adoc[tag=undoredo] +// include::../DeveloperGuide.adoc[tag=undoredo] -include::../DeveloperGuide.adoc[tag=dataencryption] +// include::../DeveloperGuide.adoc[tag=dataencryption] == PROJECT: PowerPointLabs diff --git a/docs/team/kschiew.adoc b/docs/team/kschiew.adoc new file mode 100644 index 00000000000..8a3740b76c3 --- /dev/null +++ b/docs/team/kschiew.adoc @@ -0,0 +1,116 @@ += Chiew Kok Seng - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +:toc: +:toclevels: 3 +:toc-title: +:toc-placement: preamble +:sectnums: +:xrefstyle: full +:experimental: +ifdef::env-github[] +:tip-caption: :bulb: +:note-caption: :information_source: +endif::[] +:repoURL: https://github.com/AY1920S2-CS2103T-W17-1/main + +== PROJECT: FlashSpeed + +--- + +== Overview + +FlashSpeed is a text-based flashcard application specifically designed for university students +who are learning a foreign language. +University students often have hectic schedules. +With this in mind, +FlashSpeed was created to allow students to be able to study and revise foreign vocabulary on the fly. + +FlashSpeed was developed as a team project for the CS2103T Software Engineering module in the National University of Singapore +(NUS). It is considered a brownfield project as it was built on top of the AddressBook-Level3 project created by the https://se-education.org[SE-EDU initiative]. +FlashSpeed is written in Java and has a GUI created using https://openjfx.io/[JavaFX], with a storage system implemented using +https://github.com/FasterXML/jackson[Jackson]. + + +== Summary of contributions + +* *Major enhancement 1*: designed the *architecture* of the app. +** What it does: creates the backbone structure of the application. +** Justification: This contribution is critical to the development of the product +because it created the basic structure of the app as well as designed the logic of the application, +so that different features could be developed simultaneously. +** Highlights: This contribution sets the tone of the application. +It required an in-depth analysis of how the application should work in the future. +The implementation too was challenging as it required making high-level design choices. +** Credits: Some of the high-level structure of FlashSpeed is inspired by Anki, a digital flashcard application. + +* *Major enhancement 2*: added the *storage function* to the app. +** What it does: allows the user to save and load the decks of cards created by the user to/from a local directory. +** Justification: This feature improves the usability of the product significantly +because a user needs to be able to retrieve the decks of cards that he/she has created in the past. +** Highlights: This enhancement requires an deep understanding of how JSON files and the Jackson library. +The implementation was quite challenging as there are quite a few layers of abstraction in the storage system +implemented by the original project that FlashSpeed is built upon. + +* *Major enhancement 3*: added the ability to *generate statistics* after every game session. +** What it does: allows the user to look at the statistics related to the game session that he/she played. +** Justification: This feature increases the usage rate of the app, +because it provides a feedback on the performance of the user. +By quantifying the performance, this gives user motivation to do better in subsequent game sessions. +Thus, the user will start more game sessions and hence the app +usage time will increase. +** Highlights: The difficulty was manageable, +as most of the enhancement was essentially arithmetic manipulation. + +* *Minor enhancement*: Implemented the library class that contains the decks of cards along with another teammate, +LiXin. Built and enhanced the Model and Logic Component with Amirul and LiXin. + +* *Code contributed*: [https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=&sort=groupTitle&sortWithin=title&since=2020-02-14&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=kschiew&tabRepo=AY1920S2-CS2103T-W17-1%2Fmain%5Bmaster%5D[Functional and Test code]] + +* *Other contributions*: + +** Project management: +*** Opened https://github.com/AY1920S2-CS2103T-W17-1/main/issues?q=author%3Akschiew+[22 issues], and got assigned https://github.com/AY1920S2-CS2103T-W17-1/main/issues?q=assignee%3Akschiew[17 issues] on GitHub. +** Enhancements to existing features: +*** Created test utilities for teammates to utilize during testing (Pull requests https://github.com/AY1920S2-CS2103T-W17-1/main/pull/300[#300], https://github.com/AY1920S2-CS2103T-W17-1/main/pull/285[#285]) +*** Wrote additional tests for existing features to increase coverage +(Pull requests https://github.com/AY1920S2-CS2103T-W17-1/main/pull/315[#36] +https://github.com/AY1920S2-CS2103T-W17-1/main/pull/377[#377]) +** Documentation: +*** Changed the language used in the User Guide to be more user-friendly (Pull requests https://github.com/AY1920S2-CS2103T-W17-1/main/pull/170[#170] https://github.com/AY1920S2-CS2103T-W17-1/main/pull/201[#201]) +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com/AY1920S2-CS2103T-W17-1/main/pull/295[#295] +https://github.com/AY1920S2-CS2103T-W17-1/main/pull/84[#84] +https://github.com/AY1920S2-CS2103T-W17-1/main/pull/347[#347] +*** Reported bugs and suggestions for other teams in the class (examples: +https://github.com/kschiew/ped/issues/1[#1] https://github.com/kschiew/ped/issues/2[#2] +https://github.com/kschiew/ped/issues/3[#3] https://github.com/kschiew/ped/issues/4[#4] +** Tools: +*** Integrated a third party library (Jackson) to the project (https://github.com/AY1920S2-CS2103T-W17-1/main/pull/119[#119]) + +== 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=createdeck] +include::../UserGuide.adoc[tag=addcard] + +//include::../UserGuide.adoc[] + +== 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=storage] +include::../DeveloperGuide.adoc[tag=createdeck] +include::../DeveloperGuide.adoc[tag=addcard] + + +--- diff --git a/docs/team/lacedaemon98.adoc b/docs/team/lacedaemon98.adoc new file mode 100644 index 00000000000..5845aebad09 --- /dev/null +++ b/docs/team/lacedaemon98.adoc @@ -0,0 +1,92 @@ += Pham Tran Tuan Linh - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: FlashSpeed +image::flashspeed_logo.png[width="100", align="left"] +--- + +== Overview + +FlashSpeed is a text-based flashcard application specifically designed for university students who are learning a foreign language. University students often have hectic schedules. With this in mind, FlashSpeed was created to allow students to be able to study and revise foreign vocabulary on the fly. With a single command, you can start a quick study session on FlashSpeed whenever! + +Keeping, flipping, and tracking physical flashcards can be a pain. FlashSpeed enhances the studying process by having virtual flashcards and a smarter review system. Users will be tested more frequently on flashcards that they had trouble memorizing previously. + +By using FlashSpeed, you will learn faster and remember for longer! + +== Summary of contributions + +* *Major enhancement 1*: Develop everything related to User Interface (UI) and User Experience (UX): colour scheme, displaying contents, placement of contents. +** What it does: Allows the user to view and interact with the application +** Justification: This component is crucial to the application because it visualises the whole application in order for it to function. +** Highlights: This component requires a lot of research, planning and in-depth analysis of the behaviours of all existing and future features. E.g: When and how the scenes should be switched?; JavaFX and FXML research; When the user updates a content, how to show the updated content immediately? + + + +* *Major enhancement 2*: Modify `LogicManager` and `ModelManager` classes and integrate them with GUI. +** What it does: `LogicManager` and `ModelManager` facilitate the logic behind every command made by the user and reflect the changes on GUI. +** Justification: These components act as the brain cells of FlashSpeed to process every operation. +** Highlights: These components require in-depth understanding on the behaviours and the logic behind all features. + + + +* *Minor enhancement*: +** Contribute to the logic behind *Play view* commands and `select` command. +** Design the application logo + +.Draft version (left) and Final version (right) of FlashSpeed logo +image::logos.jpg[width="300", align="center"] + + + +* *Code contributed*: [https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=lacedaemon98&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=lacedaemon98&tabRepo=AY1920S2-CS2103T-W17-1%2Fmain%5Bmaster%5D[Functional & Test code]] + + + +* *Other contributions*: + +** Project management: +*** Actively initiate discussions and generate ideas outside meetings. +** Enhancements to existing features: +*** Display help without Internet connection needed by showing the User Guide directly when using `help` instead of just showing the link: https://github.com/AY1920S2-CS2103T-W17-1/main/pull/194[#194] https://github.com/AY1920S2-CS2103T-W17-1/main/pull/195[#195] +*** Spot and fix bugs in other members' features such as `reset`, `stop`, `exit`, etc.: https://github.com/AY1920S2-CS2103T-W17-1/main/pull/271[#271] https://github.com/AY1920S2-CS2103T-W17-1/main/pull/175[#175] https://github.com/AY1920S2-CS2103T-W17-1/main/pull/193[#193] +** Community: +*** PRs reviewed: https://github.com/AY1920S2-CS2103T-W17-1/main/pull/222[#222] https://github.com/AY1920S2-CS2103T-W17-1/main/pull/335[#335] +*** Reported bugs and provided suggestions for other teams: https://github.com/lacedaemon98/ped/issues/1[#1] https://github.com/lacedaemon98/ped/issues/2[#2] https://github.com/lacedaemon98/ped/issues/3[#3] https://github.com/lacedaemon98/ped/issues/4[#4] https://github.com/lacedaemon98/ped/issues/5[#5] https://github.com/lacedaemon98/ped/issues/6[#6] https://github.com/lacedaemon98/ped/issues/7[#7] https://github.com/lacedaemon98/ped/issues/8[#8] + +** Documentation: +*** Write multiple sections (shown below) +*** Provide all the product screenshots in the User Guide and Developer Guide and update them when there are changes to the current application + +{empty} + + +== 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=views] + +{empty} + + +include::../UserGuide.adoc[tag=faq] + +{empty} + + +== 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=UI-component] + +{empty} + + +include::../DeveloperGuide.adoc[tag=selectdeck] +{empty} + + +include::../DeveloperGuide.adoc[tag=views] + + + diff --git a/docs/team/ncslzh.adoc b/docs/team/ncslzh.adoc new file mode 100644 index 00000000000..c8fb0039d90 --- /dev/null +++ b/docs/team/ncslzh.adoc @@ -0,0 +1,125 @@ += Nicholas Leong - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets +:repoURL: https://github.com/AY1920S2-CS2103T-W17-1/main/tree/master + +== PROJECT: FlashSpeed +--- +== Overview +FlashSpeed is a text-based flashcard application specifically designed for university students who are learning a foreign language. +Users are able to categorize virtually created flashcards into their own decks and launch review sessions within those decks. + + +== Summary of contributions + +My roles in this project include being in charge of the weekly agenda, minutes taker and in-charge of the Developer Guide. +Being in charge of the weekly agenda meant that I briefed team members of the upcoming tasks to be discussed every meeting. +I also took down any important discussion points needed to complete to agenda items. I also established deadlines for weekly tasks. + +* *Major enhancement*: Added the *game algorithm for the game mode.* +** What it does: +. In a game, the next card is always randomly chosen. +. If the card is answered incorrectly, the user will have to answer that card another time. +.. However, the user only needs to answer correctly 2 times in a row to clear that card. + For example, if the user gets the same card wrong 10 times in a row, he only needs to answer that card 2 times + to clear it. +.. If the user answers the card correctly one time and wrong after that. The user will still need to answer the card correctly +twice in a row. +. If the card is answered correctly on the first try, that card is cleared and the user no longer has to answer it. + +** Justification: This feature improves the product significantly because it helps the user memorize words more effectively as well +as making the app more challenging and fun to use. Users can memorize words more effectively when the ones they get wrong are shown more often. + +** Highlights: This algorithm affected existing features, such as the Statistics at the end of the game as well as the progress bar of the game. +It required the team to decide how we should re-implement the existing affected features to accommodate this enhancement. + + +* *Other major enhancement*: Added *the Card and its relevant classes.* +** What it does: Just like a traditional flashcard, a Card allows users to enter content for the front and back faces of a Card. +** Justification: This feature is essential to FlashSpeed. Without it, there is no flashcard representation in our app. +** Highlights: This enhancement required very in-depth analysis of design alternatives, such as where should we implement it, how should + it interact with upper classes (Model, UI, Storage, Deck). It required a few hours of discussion and debate before we finally settled on the final design. +** Credits: AB3 Person Class - The skeleton of the Card class was derived from that Class. + +* Minor enhancement: Re-implemented how some parsers handled user input by using Java Regex. +This was implemented to simplify how we got parameters especially for the shortcut formats that we wanted to implement for EditCardCommand. + +* Minor enhancement: Re-implemented the Help Window to display the entire User Guide instead of just the link. + +* *Code contributed*: [https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=&sort=groupTitle&sortWithin=title&since=2020-02-14&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=ncslzh&tabRepo=AY1920S2-CS2103T-W17-1%2Fmain%5Bmaster%5D[Functional code]] [https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=&sort=groupTitle&sortWithin=title&since=2020-02-14&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=ncslzh&tabRepo=AY1920S2-CS2103T-W17-1%2Fmain%5Bmaster%5D[Test code]] +** > 5000 LoC +** *Note:* Although there is a huge difference in LOC shown on tP Dashboard, my team contributed very evenly to functional code. + +* *Other contributions:* +** Project management: +*** Setting up Github and Travis +*** Setting up and maintaining the Milestone Tracker +*** Maintaining most of the issues on issue tracker + +** *Enhancements to existing features:* +*** Merged two general parsers into one big master parser https://github.com/AY1920S2-CS2103T-W17-1/main/pull/97[#97] +*** Add validation checks for commands to prevent some commands from being used in certain modes https://github.com/AY1920S2-CS2103T-W17-1/main/pull/145[#145] + +** *Documentation contributed:* +*** Developer Guide: +**** In-charge of document quality +**** Reformatted the structure and layout for ease of readability and navigability (e.g. topic headings, section spacing, etc.) +**** Wrote introduction +**** Wrote the entire Appendix A-G + + +*** Others: +**** Updated AB3 naming found in any documents into FlashSpeed https://github.com/AY1920S2-CS2103T-W17-1/main/pull/159[#159] +**** Removed unwanted documents https://github.com/AY1920S2-CS2103T-W17-1/main/pull/189[#189] +**** Changed website header https://github.com/AY1920S2-CS2103T-W17-1/main/pull/190[#190] + +*** Review/mentoring contributions: +**** PRs reviewed: https://github.com/AY1920S2-CS2103T-W17-1/main/pull/75[#75] https://github.com/AY1920S2-CS2103T-W17-1/main/pull/84[#84] https://github.com/AY1920S2-CS2103T-W17-1/main/pull/295[#295] https://github.com/AY1920S2-CS2103T-W17-1/main/pull/348[#348] + +== 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=renameAndPlay] + +<<< + +== 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=introduction] + +--- + +include::../DeveloperGuide.adoc[tag=design-logic] + +--- + +include::../DeveloperGuide.adoc[tag=create-deck-consideration] + +--- + +include::../DeveloperGuide.adoc[tag=design-consideration-play] + +--- + +include::../DeveloperGuide.adoc[tag=appendixC] + +{more at Appendix C in DG} + +--- + +include::../DeveloperGuide.adoc[tag=appendixE] + +{Appendix naming is not correct here, more at Appendix E in DG} + +--- + +include::../DeveloperGuide.adoc[tag=appendixG] + +{more at Appendix G in DG} + diff --git a/docs/team/olixino.adoc b/docs/team/olixino.adoc new file mode 100644 index 00000000000..1c82d57641e --- /dev/null +++ b/docs/team/olixino.adoc @@ -0,0 +1,85 @@ += Liao Li Xin - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets +:experimental: + + +== PROJECT: FlashSpeed + +--- + +== Overview +FlashSpeed is a text-based flashcard application specifically designed for university students who are learning a foreign language. +FlashSpeed was created to allow students to be able to study and revise foreign vocabulary on the fly. + +If you are currently learning a new language or interested to do so, FlashSpeed will be yor best companion and helper along +the learning journey. + +== Summary of contributions + +* *Major enhancement 1*: added the *play view* of the app. +** What it does: allows users to start a new game session to test their memory of the vocabulary. +** Justification: This feature is one of the essence of the app and it improves the product significantly in terms of functionality +because users not only can store and memorize vocabulary, but also can test their memory by playing a game. +** Highlights: This enhancement affects other commands in different view since some commands can only be executed in one view. +It requires more validations to be introduced to other commands as well as changes to the Model component to ensure isolation +in different views. + +* *Major enhancement 2*: implemented the *model manager* class of the app. +*** What it does: enables the Model component to handle all possible commands to be made by the users. +*** Justification: This enhancement allows every operation to be executed at the Model level and increases the maintainability and +scalability of the code since all operations are handled at one same place. +*** Highlight: This enhancement requires an in-depth understanding of the overall architecture of the product and changes to be made to other related classes. + +* *Minor enhancement*: added create/remove/rename deck command and add/delete card commands as the core functionality of the app. + + +* *Code contributed*: [https://nus-cs2103-ay1920s2.github.io/tp-dashboard/#search=olixino&sort=groupTitle&sortWithin=title&since=2020-02-14&timeframe=commit&mergegroup=false&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=oLiXino&tabRepo=AY1920S2-CS2103T-W17-1%2Fmain%5Bmaster%5D[Functional and Test code]] + +* *Other contributions*: + +** Enhancements to existing features: +*** added stop command to allow users to end the game session halfway (Pull request https://github.com/AY1920S2-CS2103T-W17-1/main/pull/173[#173]) +*** fixed bugs during testing (Pull requests https://github.com/AY1920S2-CS2103T-W17-1/main/pull/198[#198], +https://github.com/AY1920S2-CS2103T-W17-1/main/pull/276[#276], https://github.com/AY1920S2-CS2103T-W17-1/main/pull/286[#286]) +** Documentation: +*** Refine Play View section of User Guide to be more user friendly. (Pull request https://github.com/AY1920S2-CS2103T-W17-1/main/pull/225[#255]) +** Community: +*** Reported bugs and provided suggestions for other teams in the class (examples: +https://github.com/oLiXino/ped/issues/1[#1] https://github.com/oLiXino/ped/issues/2[#2] https://github.com/oLiXino/ped/issues/3[#3]) + + + +== Contributions to the User Guide + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=play-view-language] + +--- + +include::../UserGuide.adoc[tag=flip-language] + +--- + +include::../UserGuide.adoc[tag=answer-language] + +--- + +include::../UserGuide.adoc[tag=stop-language] + +== 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=model] + +--- + +include::../DeveloperGuide.adoc[tag=play] + diff --git a/docs/templates/_header.html.slim b/docs/templates/_header.html.slim index 3c2d5aed43c..c596ecea643 100644 --- a/docs/templates/_header.html.slim +++ b/docs/templates/_header.html.slim @@ -1,26 +1,4 @@ / NOTE: You must restart the gradle daemon after modifying any template file for the changes to take effect. -- if !(attr? 'no-site-header') && (attr? 'site-seedu') - #seedu-header - nav.navbar.navbar-lg.navbar-light.bg-lighter - .container - a.navbar-brand href='https://se-edu.github.io/' - img src=(site_url 'images/SeEduLogo.png') alt='SE-EDU' - ul.navbar-nav - li.nav-item - a.nav-link href='https://se-edu.github.io/addressbook-level1' AB-1 - li.nav-item - a.nav-link href='https://se-edu.github.io/addressbook-level2' AB-2 - li.nav-item - a.nav-link.active href=(site_url 'index.html') AB-3 - li.nav-item - a.nav-link href='https://se-edu.github.io/addressbook-level4' AB-4 - li.nav-item - a.nav-link href='https://se-edu.github.io/collate' Collate - li.nav-item - a.nav-link href='https://se-edu.github.io/se-book' Book - li.nav-item - a.nav-link href='https://se-edu.github.io/learningresources' Resources - - if !(attr? 'no-site-header') #site-header nav.navbar.navbar-light.bg-light @@ -32,9 +10,6 @@ =nav_link('UserGuide', 'UserGuide.html', 'User Guide') li.nav-item =nav_link('DeveloperGuide', 'DeveloperGuide.html', 'Developer Guide') - - if attr? 'site-seedu' - li.nav-item - =nav_link('LearningOutcomes', 'LearningOutcomes.html', 'LOs') li.nav-item =nav_link('AboutUs', 'AboutUs.html', 'About Us') li.nav-item diff --git a/docs/tutorials/AddRemark.adoc b/docs/tutorials/AddRemark.adoc index ea388068303..d576122952b 100644 --- a/docs/tutorials/AddRemark.adoc +++ b/docs/tutorials/AddRemark.adoc @@ -38,7 +38,7 @@ We accomplish that by returning a `CommandResult` with an accompanying message. ---- package seedu.address.logic.commands; -import seedu.address.model.Model; +import Model; /** * Changes the remark of an existing person in the address book. @@ -251,7 +251,7 @@ That means we should add a `Remark` class so that we can use a `Remark` object t === Add a new `Remark` class -Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. +Create a new `Remark` in `deck`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. A copy-paste and search-replace later, you should have something like link:https://github.com/nus-cs2103-AY1920S1/addressbook-level3/commit/b7a47c50c8e5f0430d343a23d2863446b6ce9298#diff-af2f075d24dfcd333876f0fbce321f25[this]. Note how `Remark` has no constrains and thus does not require input validation. @@ -266,14 +266,16 @@ These should be relatively simple changes. Without getting too deep into `fxml`, let's go on a 5 minute adventure to get some placeholder text to show up for each person. Simply add -[source, java] + +[source,java] .PersonCard.java -``` +[source] +---- @FXML private Label remark; -``` +---- -to link:https://github.com/nus-cs2103-AY1920S1/addressbook-level3/commit/2758455583f0101ed918a318fc75679270843a0d#diff-0c6b6abcfac8c205e075294f25e851fe[`seedu.address.ui.PersonCard`]. +to link:https://github.com/nus-cs2103-AY1920S1/addressbook-level3/commit/2758455583f0101ed918a318fc75679270843a0d#diff-0c6b6abcfac8c205e075294f25e851fe[`DeckCard`]. `@FXML` is an annotation that marks a private or protected field and makes it accessible to FXML. It might sound like Greek to you right now, don't worry -- we will get back to it later. diff --git a/docs/tutorials/RemovingFields.adoc b/docs/tutorials/RemovingFields.adoc index 5a50b6965a6..de216f3d7d1 100644 --- a/docs/tutorials/RemovingFields.adoc +++ b/docs/tutorials/RemovingFields.adoc @@ -104,7 +104,7 @@ While keeping the `address` field in the json files does not cause the tests to .invalidPersonAddressBook.json [source, json] -``` +---- { "persons": [ { "name": "Person with invalid name field: Ha!ns Mu@ster", @@ -113,5 +113,5 @@ While keeping the `address` field in the json files does not cause the tests to "address": "4th street" } ] } -``` +---- You can go through each individual `json` file and manually remove the `address` field. diff --git a/docs/tutorials/TracingCode.adoc b/docs/tutorials/TracingCode.adoc index 5f0aaba1741..dbdba461853 100644 --- a/docs/tutorials/TracingCode.adoc +++ b/docs/tutorials/TracingCode.adoc @@ -49,7 +49,7 @@ However, the execution path through a GUI is often somewhat obscure due to vario used by GUI frameworks, which happens to be the case here too. Therefore, let us put the breakpoint where the UI transfers control to the Logic component. According to the sequence diagram, the UI component yields control to the Logic component through a method named `execute`. Searching through the code base for `execute()` yields a promising candidate in -`seedu.address.ui.CommandBox.CommandExecutor`. +`CommandBox.CommandExecutor`. .Using the `Search for target by name` feature. `Navigate` > `Symbol`. image::Execute.png[] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 44e7c4d1d7b..fb33076497b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Sat Mar 07 00:49:20 SGT 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/com/flashspeed/AppParameters.java similarity index 93% rename from src/main/java/seedu/address/AppParameters.java rename to src/main/java/com/flashspeed/AppParameters.java index ab552c398f3..5d718fa6e4d 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/com/flashspeed/AppParameters.java @@ -1,4 +1,4 @@ -package seedu.address; +package com.flashspeed; import java.nio.file.Path; import java.nio.file.Paths; @@ -6,9 +6,10 @@ import java.util.Objects; import java.util.logging.Logger; +import com.flashspeed.commons.core.LogsCenter; +import com.flashspeed.commons.util.FileUtil; + import javafx.application.Application; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.FileUtil; /** * Represents the parsed command-line parameters given to the application. diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/com/flashspeed/Main.java similarity index 97% rename from src/main/java/seedu/address/Main.java rename to src/main/java/com/flashspeed/Main.java index 052a5068631..3bfb891ef8f 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/com/flashspeed/Main.java @@ -1,4 +1,4 @@ -package seedu.address; +package com.flashspeed; import javafx.application.Application; diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/com/flashspeed/MainApp.java similarity index 67% rename from src/main/java/seedu/address/MainApp.java rename to src/main/java/com/flashspeed/MainApp.java index e5cfb161b73..ebb7795c4da 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/com/flashspeed/MainApp.java @@ -1,42 +1,43 @@ -package seedu.address; +package com.flashspeed; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; import java.util.logging.Logger; +import com.flashspeed.commons.core.Config; +import com.flashspeed.commons.core.LogsCenter; +import com.flashspeed.commons.core.Version; +import com.flashspeed.commons.exceptions.DataConversionException; +import com.flashspeed.commons.util.ConfigUtil; +import com.flashspeed.commons.util.StringUtil; +import com.flashspeed.logic.Logic; +import com.flashspeed.logic.LogicManager; +import com.flashspeed.model.Library; +import com.flashspeed.model.Model; +import com.flashspeed.model.ModelManager; +import com.flashspeed.model.ReadOnlyLibrary; +import com.flashspeed.model.ReadOnlyUserPrefs; +import com.flashspeed.model.UserPrefs; +import com.flashspeed.model.util.SampleDataUtil; +import com.flashspeed.storage.JsonLibraryStorage; +import com.flashspeed.storage.JsonUserPrefsStorage; +import com.flashspeed.storage.LibraryStorage; +import com.flashspeed.storage.Storage; +import com.flashspeed.storage.StorageManager; +import com.flashspeed.storage.UserPrefsStorage; +import com.flashspeed.ui.Ui; +import com.flashspeed.ui.UiManager; + import javafx.application.Application; import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.core.Version; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.ConfigUtil; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; -import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; -import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; -import seedu.address.storage.JsonAddressBookStorage; -import seedu.address.storage.JsonUserPrefsStorage; -import seedu.address.storage.Storage; -import seedu.address.storage.StorageManager; -import seedu.address.storage.UserPrefsStorage; -import seedu.address.ui.Ui; -import seedu.address.ui.UiManager; /** * Runs the application. */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 6, 0, true); + public static final Version VERSION = new Version(1, 4, 0, false); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -48,7 +49,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing FlashSpeed ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -56,8 +57,9 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); UserPrefs userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + LibraryStorage libraryStorage = new JsonLibraryStorage(userPrefs.getLibraryFilePath()); + storage = new StorageManager(libraryStorage, userPrefsStorage); + initLogging(config); @@ -66,28 +68,31 @@ public void init() throws Exception { logic = new LogicManager(model, storage); ui = new UiManager(logic); + } + /** - * 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 library and {@code userPrefs}.
+ * The data from the sample library will be used instead if {@code storage}'s library is not found, + * or an empty library will be used instead if errors occur when reading {@code storage}'s library. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional libraryOptional; + ReadOnlyLibrary initialData; + try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + libraryOptional = storage.readLibrary(); + if (!libraryOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample library"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = libraryOptional.orElseGet(SampleDataUtil::getSampleLibrary); } 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); @@ -151,7 +156,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(); } @@ -167,13 +172,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting FlashSpeed " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping FlashSpeed ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/com/flashspeed/commons/core/Config.java similarity index 97% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/com/flashspeed/commons/core/Config.java index 91145745521..b28897a3255 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/com/flashspeed/commons/core/Config.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package com.flashspeed.commons.core; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/com/flashspeed/commons/core/GuiSettings.java similarity index 98% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/com/flashspeed/commons/core/GuiSettings.java index 5ace559ad15..82b55049ee6 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/com/flashspeed/commons/core/GuiSettings.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package com.flashspeed.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/com/flashspeed/commons/core/LogsCenter.java similarity index 97% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/com/flashspeed/commons/core/LogsCenter.java index 431e7185e76..eb2589b48af 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/com/flashspeed/commons/core/LogsCenter.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package com.flashspeed.commons.core; import java.io.IOException; import java.util.Arrays; @@ -18,7 +18,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "flashspeed.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/com/flashspeed/commons/core/Messages.java b/src/main/java/com/flashspeed/commons/core/Messages.java new file mode 100644 index 00000000000..76546768acb --- /dev/null +++ b/src/main/java/com/flashspeed/commons/core/Messages.java @@ -0,0 +1,17 @@ +package com.flashspeed.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_INPUT_CONTAINS_COLON = "Card face value(s) should not contain colon(s)!"; + + public static final String MESSAGE_INVALID_DECK_DISPLAYED_INDEX = "The deck index provided is invalid!"; + public static final String MESSAGE_INVALID_CARD_DISPLAYED_INDEX = "The card index provided is invalid!"; + public static final String MESSAGE_DECKS_LISTED_OVERVIEW = "%1$d decks listed!"; + public static final String MESSAGE_NOT_IN_DECK_VIEW = "Currently not in a deck!"; + public static final String MESSAGE_NOT_IN_LIBRARY_VIEW = "Currently not in library mode!"; +} diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/com/flashspeed/commons/core/Version.java similarity index 98% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/com/flashspeed/commons/core/Version.java index e117f91b3b2..f7592f1687a 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/com/flashspeed/commons/core/Version.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package com.flashspeed.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/com/flashspeed/commons/core/index/Index.java similarity index 97% rename from src/main/java/seedu/address/commons/core/index/Index.java rename to src/main/java/com/flashspeed/commons/core/index/Index.java index 19536439c09..08fca2be934 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/com/flashspeed/commons/core/index/Index.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core.index; +package com.flashspeed.commons.core.index; /** * Represents a zero-based or one-based index. diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/com/flashspeed/commons/exceptions/DataConversionException.java similarity index 84% rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java rename to src/main/java/com/flashspeed/commons/exceptions/DataConversionException.java index 1f689bd8e3f..47a84801072 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java +++ b/src/main/java/com/flashspeed/commons/exceptions/DataConversionException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package com.flashspeed.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/com/flashspeed/commons/exceptions/IllegalValueException.java similarity index 92% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/com/flashspeed/commons/exceptions/IllegalValueException.java index 19124db485c..a5e3df603e4 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/com/flashspeed/commons/exceptions/IllegalValueException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package com.flashspeed.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/com/flashspeed/commons/util/AppUtil.java similarity index 93% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/com/flashspeed/commons/util/AppUtil.java index da90201dfd6..c5f4b78f2f5 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/com/flashspeed/commons/util/AppUtil.java @@ -1,9 +1,10 @@ -package seedu.address.commons.util; +package com.flashspeed.commons.util; import static java.util.Objects.requireNonNull; +import com.flashspeed.MainApp; + import javafx.scene.image.Image; -import seedu.address.MainApp; /** * A container for App specific utility functions diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/com/flashspeed/commons/util/CollectionUtil.java similarity index 96% rename from src/main/java/seedu/address/commons/util/CollectionUtil.java rename to src/main/java/com/flashspeed/commons/util/CollectionUtil.java index eafe4dfd681..2c587475c12 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/com/flashspeed/commons/util/CollectionUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package com.flashspeed.commons.util; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/com/flashspeed/commons/util/ConfigUtil.java similarity index 77% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/com/flashspeed/commons/util/ConfigUtil.java index f7f8a2bd44c..08ef6b5b924 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/com/flashspeed/commons/util/ConfigUtil.java @@ -1,11 +1,11 @@ -package seedu.address.commons.util; +package com.flashspeed.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 com.flashspeed.commons.core.Config; +import com.flashspeed.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/com/flashspeed/commons/util/FileUtil.java similarity index 98% rename from src/main/java/seedu/address/commons/util/FileUtil.java rename to src/main/java/com/flashspeed/commons/util/FileUtil.java index b1e2767cdd9..8b8ec2d5ac2 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/com/flashspeed/commons/util/FileUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package com.flashspeed.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/com/flashspeed/commons/util/JsonUtil.java similarity index 97% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/com/flashspeed/commons/util/JsonUtil.java index 8ef609f055d..94266cd6091 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/com/flashspeed/commons/util/JsonUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package com.flashspeed.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 com.flashspeed.commons.core.LogsCenter; +import com.flashspeed.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/com/flashspeed/commons/util/StringUtil.java similarity index 88% rename from src/main/java/seedu/address/commons/util/StringUtil.java rename to src/main/java/com/flashspeed/commons/util/StringUtil.java index 61cc8c9a1cb..e4276c93f95 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/com/flashspeed/commons/util/StringUtil.java @@ -1,7 +1,6 @@ -package seedu.address.commons.util; +package com.flashspeed.commons.util; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; import java.io.PrintWriter; import java.io.StringWriter; @@ -28,8 +27,8 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { requireNonNull(word); String preppedWord = word.trim(); - checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty"); - checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word"); + AppUtil.checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty"); + AppUtil.checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word"); String preppedSentence = sentence; String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); diff --git a/src/main/java/com/flashspeed/logic/Logic.java b/src/main/java/com/flashspeed/logic/Logic.java new file mode 100644 index 00000000000..33f5dad6dfc --- /dev/null +++ b/src/main/java/com/flashspeed/logic/Logic.java @@ -0,0 +1,104 @@ +package com.flashspeed.logic; + +import java.nio.file.Path; + +import com.flashspeed.commons.core.GuiSettings; +import com.flashspeed.logic.commands.CommandResult; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.logic.parser.exceptions.ParseException; +import com.flashspeed.model.ReadOnlyLibrary; +import com.flashspeed.model.deck.Deck; +import com.flashspeed.model.deck.card.Card; +import com.flashspeed.model.util.View; + +import javafx.beans.property.ReadOnlyProperty; +import javafx.collections.ObservableList; + +/** + * 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 a Read-Only copy of the current Library + */ + ReadOnlyLibrary getLibrary(); + + /** + * Returns an unmodifiable view of the filtered list of decks + */ + ObservableList getFilteredDeckList(); + + /** + * Returns the user prefs' library file path + */ + Path getLibraryFilePath(); + + /** + * Returns the user prefs' GUI settings + */ + GuiSettings getGuiSettings(); + + /** + * Sets the user prefs' GUI settings + */ + void setGuiSettings(GuiSettings guiSettings); + + /** + * Returns the currently selected Deck + */ + Deck getCurrentDeck(); + + /** + * Returns the current View of the Model + */ + View getView(); + + /** + * Sets selected deck in the Model + * @param deck target deck + */ + void setSelectedDeck(Deck deck); + + //===Read-Only Property components for UI============================================== + + /** + * Returns the selected Deck Read-only Property + */ + ReadOnlyProperty selectedDeckProperty(); + + /** + * Returns the current View Read-only Property + */ + ReadOnlyProperty currentViewProperty(); + + /** + * Returns the currently playing Card Read-only Property + */ + ReadOnlyProperty playingCardProperty(); + + /** + * Returns the current Card flipped status Read-Only Property + */ + ReadOnlyProperty flippedProperty(); + + /** + * Returns the no of Cards attempted Read-Only Property + */ + ReadOnlyProperty cardAttemptedProperty(); + + /** + * Returns the no of Cards remaining Read-Only Property + */ + ReadOnlyProperty cardRemainingProperty(); + +} diff --git a/src/main/java/com/flashspeed/logic/LogicManager.java b/src/main/java/com/flashspeed/logic/LogicManager.java new file mode 100644 index 00000000000..50d82c3b2ed --- /dev/null +++ b/src/main/java/com/flashspeed/logic/LogicManager.java @@ -0,0 +1,184 @@ +package com.flashspeed.logic; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.logging.Logger; + +import com.flashspeed.commons.core.GuiSettings; +import com.flashspeed.commons.core.LogsCenter; +import com.flashspeed.logic.commands.Command; +import com.flashspeed.logic.commands.CommandResult; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.logic.parser.MasterParser; +import com.flashspeed.logic.parser.exceptions.ParseException; +import com.flashspeed.model.Model; +import com.flashspeed.model.ReadOnlyLibrary; +import com.flashspeed.model.deck.Deck; +import com.flashspeed.model.deck.card.Card; +import com.flashspeed.model.util.View; +import com.flashspeed.storage.Storage; + +import javafx.beans.property.ReadOnlyProperty; +import javafx.collections.ObservableList; + + +/** + * The main LogicManager of the app. + */ +public class LogicManager implements Logic { + public static final String FILE_OPS_ERROR_MESSAGE = "Could not save data to file: "; + private final Logger logger = LogsCenter.getLogger(LogicManager.class); + + private final Model model; + private final Storage storage; + private final MasterParser masterParser; + + /** + * Initializes LogicManager with the given Model and Storage. + */ + public LogicManager(Model model, Storage storage) { + this.model = model; + this.storage = storage; + this.masterParser = new MasterParser(); + } + + /** + * 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 + */ + @Override + public CommandResult execute(String commandText) throws CommandException, ParseException { + logger.info("----------------[USER COMMAND][" + commandText + "]"); + + CommandResult commandResult; + Command command; + command = masterParser.parseCommand(commandText); + commandResult = command.execute(model); + try { + storage.saveLibrary(model.getLibrary()); + } catch (IOException ioe) { + throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); + } + + return commandResult; + } + + /** + * Returns a Read-Only copy of the current Library + */ + @Override + public ReadOnlyLibrary getLibrary() { + return model.getLibrary(); + } + + /** + * Returns an unmodifiable view of the filtered list of decks + */ + @Override + public ObservableList getFilteredDeckList() { + return model.getFilteredDeckList(); + } + + /** + * Returns the user prefs' library file path + */ + @Override + public Path getLibraryFilePath() { + return model.getLibraryFilePath(); + } + + /** + * Returns the user prefs' GUI settings + */ + @Override + public GuiSettings getGuiSettings() { + return model.getGuiSettings(); + } + + /** + * Sets the user prefs' GUI settings + */ + @Override + public void setGuiSettings(GuiSettings guiSettings) { + model.setGuiSettings(guiSettings); + } + + /** + * Returns the currently selected Deck + */ + @Override + public Deck getCurrentDeck() { + return model.getCurrentDeck(); + } + + /** + * Returns the current View of the Model + */ + @Override + public View getView() { + return model.getView(); + } + + /** + * Sets selected deck in the Model + * @param deck target deck + */ + @Override + public void setSelectedDeck(Deck deck) { + model.setSelectedDeck(deck); + } + + //===Read-Only Property components for UI============================================== + + /** + * Returns the selected Deck Read-only Property + */ + @Override + public ReadOnlyProperty selectedDeckProperty() { + return model.selectedDeckProperty(); + } + + /** + * Returns the current View Read-only Property + */ + @Override + public ReadOnlyProperty currentViewProperty() { + return model.currentViewProperty(); + } + + /** + * Returns the currently playing Card Read-only Property + */ + @Override + public ReadOnlyProperty playingCardProperty() { + return model.playingCardProperty(); + } + + /** + * Returns the current Card flipped status Read-Only Property + */ + @Override + public ReadOnlyProperty flippedProperty() { + return model.flippedProperty(); + } + + /** + * Returns the no of Cards attempted Read-Only Property + */ + @Override + public ReadOnlyProperty cardAttemptedProperty() { + return model.cardAttemptedProperty(); + } + + /** + * Returns the no of Cards remaining Read-Only Property + */ + @Override + public ReadOnlyProperty cardRemainingProperty() { + return model.cardRemainingProperty(); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/com/flashspeed/logic/commands/Command.java similarity index 78% rename from src/main/java/seedu/address/logic/commands/Command.java rename to src/main/java/com/flashspeed/logic/commands/Command.java index 64f18992160..ad29e2a91be 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/com/flashspeed/logic/commands/Command.java @@ -1,7 +1,7 @@ -package seedu.address.logic.commands; +package com.flashspeed.logic.commands; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.model.Model; /** * Represents a command with hidden internal logic and the ability to be executed. @@ -16,5 +16,4 @@ public abstract class Command { * @throws CommandException If an error occurs during command execution. */ public abstract CommandResult execute(Model model) throws CommandException; - } diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/com/flashspeed/logic/commands/CommandResult.java similarity index 69% rename from src/main/java/seedu/address/logic/commands/CommandResult.java rename to src/main/java/com/flashspeed/logic/commands/CommandResult.java index 92f900b7916..a716d4ae4f2 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/com/flashspeed/logic/commands/CommandResult.java @@ -1,9 +1,11 @@ -package seedu.address.logic.commands; +package com.flashspeed.logic.commands; import static java.util.Objects.requireNonNull; import java.util.Objects; +import com.flashspeed.model.Statistics; + /** * Represents the result of a command execution. */ @@ -17,21 +19,28 @@ public class CommandResult { /** The application should exit. */ private final boolean exit; + /** The play mode is stopped */ + private final boolean stop; + + private Statistics statistics; /** * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + public CommandResult(String feedbackToUser, boolean showHelp, boolean exit, boolean stop, Statistics statistics) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; this.exit = exit; + this.stop = stop; + this.statistics = statistics; } + /** * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, * and other fields set to their default value. */ public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + this(feedbackToUser, false, false, false, null); } public String getFeedbackToUser() { @@ -46,6 +55,14 @@ public boolean isExit() { return exit; } + public boolean isStop() { + return stop; + } + + public Statistics getStatistics() { + return statistics; + } + @Override public boolean equals(Object other) { if (other == this) { @@ -60,12 +77,12 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; + && exit == otherCommandResult.exit + && stop == otherCommandResult.stop; } @Override public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); + return Objects.hash(feedbackToUser, showHelp, exit, stop, statistics); } - } diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/com/flashspeed/logic/commands/ExitCommand.java similarity index 72% rename from src/main/java/seedu/address/logic/commands/ExitCommand.java rename to src/main/java/com/flashspeed/logic/commands/ExitCommand.java index 3dd85a8ba90..d6959c2809b 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/com/flashspeed/logic/commands/ExitCommand.java @@ -1,6 +1,6 @@ -package seedu.address.logic.commands; +package com.flashspeed.logic.commands; -import seedu.address.model.Model; +import com.flashspeed.model.Model; /** * Terminates the program. @@ -8,12 +8,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 FlashSpeed..."; @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true, false, null); } - } diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/com/flashspeed/logic/commands/HelpCommand.java similarity index 58% rename from src/main/java/seedu/address/logic/commands/HelpCommand.java rename to src/main/java/com/flashspeed/logic/commands/HelpCommand.java index bf824f91bd0..bd8cbc2a01c 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/com/flashspeed/logic/commands/HelpCommand.java @@ -1,21 +1,21 @@ -package seedu.address.logic.commands; +package com.flashspeed.logic.commands; -import seedu.address.model.Model; +import com.flashspeed.model.Model; /** - * Format full help instructions for every command for display. + * Shows full help instructions for every command. */ public class HelpCommand extends Command { public static final String COMMAND_WORD = "help"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Shows program usage instructions.\n" + "Example: " + COMMAND_WORD; public static final String SHOWING_HELP_MESSAGE = "Opened help window."; @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return new CommandResult(SHOWING_HELP_MESSAGE, true, false, false, null); } } diff --git a/src/main/java/com/flashspeed/logic/commands/ResetLibraryCommand.java b/src/main/java/com/flashspeed/logic/commands/ResetLibraryCommand.java new file mode 100644 index 00000000000..22513b793cc --- /dev/null +++ b/src/main/java/com/flashspeed/logic/commands/ResetLibraryCommand.java @@ -0,0 +1,30 @@ +package com.flashspeed.logic.commands; + +import static java.util.Objects.requireNonNull; + +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.model.Library; +import com.flashspeed.model.Model; +import com.flashspeed.model.util.View; + +/** + * Clears the library. + */ +public class ResetLibraryCommand extends Command { + + public static final String COMMAND_WORD = "reset"; + public static final String MESSAGE_SUCCESS = "Library has been cleared!"; + public static final String MESSAGE_NOT_IN_VIEW_MODE = "Cannot reset library in the play view"; + + @Override + public CommandResult execute(Model model) throws CommandException { + if (model.getView() == View.PLAY) { + throw new CommandException(MESSAGE_NOT_IN_VIEW_MODE); + } + + requireNonNull(model); + model.setLibrary(new Library()); + model.returnToLibrary(); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/com/flashspeed/logic/commands/ReturnToLibraryCommand.java b/src/main/java/com/flashspeed/logic/commands/ReturnToLibraryCommand.java new file mode 100644 index 00000000000..e8e629521e3 --- /dev/null +++ b/src/main/java/com/flashspeed/logic/commands/ReturnToLibraryCommand.java @@ -0,0 +1,44 @@ +package com.flashspeed.logic.commands; + +import static java.util.Objects.requireNonNull; + +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.model.Model; +import com.flashspeed.model.util.View; + +/** + * Returns to the library view. + */ +public class ReturnToLibraryCommand extends Command { + + public static final String COMMAND_WORD = "return"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Returns to library view."; + + public static final String MESSAGE_SUCCESS = "Returned to library view."; + public static final String MESSAGE_ALREADY_IN_LIBRARY = "Already in library view!"; + public static final String MESSAGE_NOT_IN_VIEW_MODE = "Cannot return to library in the play view"; + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + if (model.getView() == View.PLAY) { + throw new CommandException(MESSAGE_NOT_IN_VIEW_MODE); + } + + if (model.getView().equals(View.LIBRARY)) { + throw new CommandException(MESSAGE_ALREADY_IN_LIBRARY); + } + + model.returnToLibrary(); + + return new CommandResult(String.format(MESSAGE_SUCCESS)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ReturnToLibraryCommand); // instanceof handles nulls + } +} diff --git a/src/main/java/com/flashspeed/logic/commands/cardcommands/AddCardCommand.java b/src/main/java/com/flashspeed/logic/commands/cardcommands/AddCardCommand.java new file mode 100644 index 00000000000..4aae15cda08 --- /dev/null +++ b/src/main/java/com/flashspeed/logic/commands/cardcommands/AddCardCommand.java @@ -0,0 +1,67 @@ +package com.flashspeed.logic.commands.cardcommands; + +import static java.util.Objects.requireNonNull; + +import com.flashspeed.commons.core.Messages; +import com.flashspeed.logic.commands.Command; +import com.flashspeed.logic.commands.CommandResult; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.model.Model; +import com.flashspeed.model.deck.card.Card; +import com.flashspeed.model.util.View; + +/** + * Adds a card to the deck. + */ +public class AddCardCommand extends Command { + + public static final String COMMAND_WORD = "add"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Adds a card to the current deck.\n" + + "Parameters: " + + "FRONT:BACK\n" + + "Example: " + COMMAND_WORD + " " + + "ありがとう:thanks"; + + public static final String MESSAGE_SUCCESS = "New card added: %1$s"; + public static final String MESSAGE_DUPLICATE_CARD = "This card already exists in the deck"; + public static final String MESSAGE_NOT_IN_VIEW_MODE = "Cannot add card in the play view"; + + private final Card toAdd; + + /** + * Creates an AddCommand to add the specified {@code Card} + */ + public AddCardCommand(Card card) { + requireNonNull(card); + toAdd = card; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + if (model.getView() == View.PLAY) { + throw new CommandException(MESSAGE_NOT_IN_VIEW_MODE); + } + + if (model.getView() != View.DECK) { + throw new CommandException(Messages.MESSAGE_NOT_IN_DECK_VIEW); + } + + if (model.hasCard(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_CARD); + } + + model.addCard(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 AddCardCommand // instanceof handles nulls + && toAdd.equals(((AddCardCommand) other).toAdd)); + } +} diff --git a/src/main/java/com/flashspeed/logic/commands/cardcommands/DeleteCardCommand.java b/src/main/java/com/flashspeed/logic/commands/cardcommands/DeleteCardCommand.java new file mode 100644 index 00000000000..3f5e2b2601e --- /dev/null +++ b/src/main/java/com/flashspeed/logic/commands/cardcommands/DeleteCardCommand.java @@ -0,0 +1,61 @@ +package com.flashspeed.logic.commands.cardcommands; + +import static java.util.Objects.requireNonNull; + +import com.flashspeed.commons.core.Messages; +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.logic.commands.Command; +import com.flashspeed.logic.commands.CommandResult; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.model.Model; +import com.flashspeed.model.deck.card.Card; +import com.flashspeed.model.util.View; + +/** + * Deletes a card from the current deck. + */ +public class DeleteCardCommand extends Command { + + public static final String COMMAND_WORD = "delete"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes a card from the current deck.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SUCCESS = "Deleted Card: %1$s"; + public static final String MESSAGE_NOT_IN_VIEW_MODE = "Cannot delete card in the play view"; + + private final Index targetIndex; + + public DeleteCardCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + if (model.getView() == View.PLAY) { + throw new CommandException(MESSAGE_NOT_IN_VIEW_MODE); + } + + if (model.getView() != View.DECK) { + throw new CommandException(Messages.MESSAGE_NOT_IN_DECK_VIEW); + } + + if (targetIndex.getZeroBased() >= model.getCurrentDeck().getSize()) { + throw new CommandException(Messages.MESSAGE_INVALID_CARD_DISPLAYED_INDEX); + } + + Card cardToDelete = model.getCard(targetIndex); + model.deleteCard(cardToDelete); + return new CommandResult(String.format(MESSAGE_SUCCESS, cardToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteCardCommand // instanceof handles nulls + && targetIndex.equals(((DeleteCardCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/com/flashspeed/logic/commands/cardcommands/EditCardCommand.java b/src/main/java/com/flashspeed/logic/commands/cardcommands/EditCardCommand.java new file mode 100644 index 00000000000..19d7e899f63 --- /dev/null +++ b/src/main/java/com/flashspeed/logic/commands/cardcommands/EditCardCommand.java @@ -0,0 +1,93 @@ +package com.flashspeed.logic.commands.cardcommands; + +import static java.util.Objects.requireNonNull; + +import com.flashspeed.commons.core.Messages; +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.logic.commands.Command; +import com.flashspeed.logic.commands.CommandResult; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.model.Model; +import com.flashspeed.model.deck.card.BackFace; +import com.flashspeed.model.deck.card.Card; +import com.flashspeed.model.deck.card.FrontFace; +import com.flashspeed.model.util.View; + +/** + * Edits a card in the current deck. + */ +public class EditCardCommand extends Command { + + public static final String COMMAND_WORD = "edit"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Edits a card in the current deck.\n" + + "Either FRONT or BACK can be omitted.\n" + + "Parameters: \n" + + "INDEX (must be a positive integer) FRONT:BACK\n" + + "or " + + "INDEX (must be a positive integer) :BACK\n" + + "or " + + "INDEX (must be a positive integer) FRONT:\n" + + "Example 1: " + COMMAND_WORD + " " + + "1 " + + "ありがとう:thanks\n" + + "Example 2: " + COMMAND_WORD + " " + + "1 " + + "ありがとう:\n" + + "Example 3: " + COMMAND_WORD + " " + + "1 " + + ":thanks"; + + public static final String MESSAGE_SUCCESS = "Card edited: %1$s"; + public static final String MESSAGE_NOT_IN_VIEW_MODE = "Cannot edit card in the play view"; + + private final Index targetIndex; + private final FrontFace front; + private final BackFace back; + private Card editedCard; + + public EditCardCommand(Index targetIndex, FrontFace front, BackFace back) { + this.targetIndex = targetIndex; + this.front = front; + this.back = back; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.getView() == View.PLAY) { + throw new CommandException(MESSAGE_NOT_IN_VIEW_MODE); + } + + if (model.getView() != View.DECK) { + throw new CommandException(Messages.MESSAGE_NOT_IN_DECK_VIEW); + } + if (targetIndex.getZeroBased() >= model.getCurrentDeck().getSize()) { + throw new CommandException(Messages.MESSAGE_INVALID_CARD_DISPLAYED_INDEX); + } + + Card oldCard = model.getCard(targetIndex); + + if (front.toString().isBlank()) { + FrontFace newFront = new FrontFace(oldCard.getFrontFace().toString()); + editedCard = new Card(newFront, back); + } else if (back.toString().isBlank()) { + BackFace newBack = new BackFace(oldCard.getBackFace().toString()); + editedCard = new Card(front, newBack); + } else { + editedCard = new Card(front, back); + } + + model.replaceCard(oldCard, editedCard); + return new CommandResult(String.format(MESSAGE_SUCCESS, editedCard)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EditCardCommand // instanceof handles nulls + && targetIndex.equals(((EditCardCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/com/flashspeed/logic/commands/deckcommands/CreateDeckCommand.java b/src/main/java/com/flashspeed/logic/commands/deckcommands/CreateDeckCommand.java new file mode 100644 index 00000000000..0d0cc9df65e --- /dev/null +++ b/src/main/java/com/flashspeed/logic/commands/deckcommands/CreateDeckCommand.java @@ -0,0 +1,62 @@ +package com.flashspeed.logic.commands.deckcommands; + +import static java.util.Objects.requireNonNull; + +import com.flashspeed.logic.commands.Command; +import com.flashspeed.logic.commands.CommandResult; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.model.Model; +import com.flashspeed.model.deck.Deck; +import com.flashspeed.model.util.View; + +/** + * Creates a deck in the library. + */ +public class CreateDeckCommand extends Command { + + public static final String COMMAND_WORD = "create"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Creates a deck in the library.\n" + + "Parameters: " + + "NAME\n" + + "Example: " + COMMAND_WORD + " " + + "Japanese 2"; + + public static final String MESSAGE_SUCCESS = "New deck added: %1$s"; + public static final String MESSAGE_DUPLICATE_DECK = "This deck already exists in the library."; + public static final String MESSAGE_NOT_IN_VIEW_MODE = "Cannot create deck in the play view"; + + private final Deck toAdd; + + /** + * Creates an CreateDeckCommand to add the specified {@code Deck} + */ + public CreateDeckCommand(Deck deck) { + requireNonNull(deck); + toAdd = deck; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.getView() == View.PLAY) { + throw new CommandException(MESSAGE_NOT_IN_VIEW_MODE); + } + + if (model.hasDeck(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_DECK); + } + + model.createDeck(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 CreateDeckCommand // instanceof handles nulls + && toAdd.equals(((CreateDeckCommand) other).toAdd)); + } +} diff --git a/src/main/java/com/flashspeed/logic/commands/deckcommands/RemoveDeckCommand.java b/src/main/java/com/flashspeed/logic/commands/deckcommands/RemoveDeckCommand.java new file mode 100644 index 00000000000..1f818486f06 --- /dev/null +++ b/src/main/java/com/flashspeed/logic/commands/deckcommands/RemoveDeckCommand.java @@ -0,0 +1,62 @@ +package com.flashspeed.logic.commands.deckcommands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import com.flashspeed.commons.core.Messages; +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.logic.commands.Command; +import com.flashspeed.logic.commands.CommandResult; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.model.Model; +import com.flashspeed.model.deck.Deck; +import com.flashspeed.model.util.View; + +/** + * Removes a deck from the library. + */ +public class RemoveDeckCommand extends Command { + + public static final String COMMAND_WORD = "remove"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Removes a deck from the library.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example:" + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_DECK_SUCCESS = "Deleted Deck: %1$s"; + public static final String MESSAGE_NOT_IN_VIEW_MODE = "Cannot remove deck in the play view"; + + private final Index targetIndex; + + public RemoveDeckCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + if (model.getView() == View.PLAY) { + throw new CommandException(MESSAGE_NOT_IN_VIEW_MODE); + } + + List lastShownList = model.getFilteredDeckList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_DECK_DISPLAYED_INDEX); + } + + Deck deckToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteDeck(deckToDelete); + + return new CommandResult(String.format(MESSAGE_DELETE_DECK_SUCCESS, deckToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RemoveDeckCommand // instanceof handles nulls + && targetIndex.equals(((RemoveDeckCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/com/flashspeed/logic/commands/deckcommands/RenameDeckCommand.java b/src/main/java/com/flashspeed/logic/commands/deckcommands/RenameDeckCommand.java new file mode 100644 index 00000000000..1ff0a2668fe --- /dev/null +++ b/src/main/java/com/flashspeed/logic/commands/deckcommands/RenameDeckCommand.java @@ -0,0 +1,83 @@ +package com.flashspeed.logic.commands.deckcommands; + +import static com.flashspeed.commons.util.CollectionUtil.requireAllNonNull; +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import com.flashspeed.commons.core.Messages; +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.logic.commands.Command; +import com.flashspeed.logic.commands.CommandResult; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.model.Model; +import com.flashspeed.model.deck.Deck; +import com.flashspeed.model.deck.Name; +import com.flashspeed.model.util.View; + +/** + * Renames the name of a deck in the library. + */ +public class RenameDeckCommand extends Command { + + public static final String COMMAND_WORD = "rename"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Renames the name of a deck in the library.\n" + + "Existing name will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "NAME \n" + + "Example: " + COMMAND_WORD + " 1 " + + "Japanese 2"; + + public static final String MESSAGE_RENAME_DECK_SUCCESS = "Edited Deck: %1$s"; + public static final String MESSAGE_NOT_EDITED = "New deck name must be provided."; + public static final String MESSAGE_DUPLICATE_DECK = "This deck name already exists in the library."; + public static final String MESSAGE_NOT_IN_VIEW_MODE = "Cannot rename deck in the play view"; + + private final Index index; + private final Name name; + + /** + * @param index of the deck in the library list to edit + * @param name new name to edit the deck with + */ + public RenameDeckCommand(Index index, Name name) { + requireAllNonNull(index, name); + this.index = index; + this.name = name; + } + + // need to remake this method + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.getView() == View.PLAY) { + throw new CommandException(MESSAGE_NOT_IN_VIEW_MODE); + } + + List lastShownList = model.getFilteredDeckList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_DECK_DISPLAYED_INDEX); + } + + boolean result = model.renameDeck(index, name); + if (!result) { + throw new CommandException(MESSAGE_DUPLICATE_DECK); + } + Deck editedDeck = lastShownList.get(index.getZeroBased()); + model.updateFilteredDeckList(Model.PREDICATE_SHOW_ALL_DECKS); + + return new CommandResult(String.format(MESSAGE_RENAME_DECK_SUCCESS, editedDeck)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RenameDeckCommand // instanceof handles nulls + && index.equals(((RenameDeckCommand) other).index) + && name.equals(((RenameDeckCommand) other).name)); + } +} diff --git a/src/main/java/com/flashspeed/logic/commands/deckcommands/SelectDeckCommand.java b/src/main/java/com/flashspeed/logic/commands/deckcommands/SelectDeckCommand.java new file mode 100644 index 00000000000..a9b19a317bc --- /dev/null +++ b/src/main/java/com/flashspeed/logic/commands/deckcommands/SelectDeckCommand.java @@ -0,0 +1,65 @@ +package com.flashspeed.logic.commands.deckcommands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import com.flashspeed.commons.core.Messages; +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.logic.commands.Command; +import com.flashspeed.logic.commands.CommandResult; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.model.Model; +import com.flashspeed.model.deck.Deck; +import com.flashspeed.model.util.View; + +/** + * Selects a deck in the library. + */ +public class SelectDeckCommand extends Command { + + public static final String COMMAND_WORD = "select"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Selects a deck in the library.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example:" + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SUCCESS = "Deck selected: %1$s"; + public static final String MESSAGE_NOT_IN_VIEW_MODE = "Cannot select deck in the play view"; + + private final Index targetIdx; + + /** + * Creates an AddCommand to add the specified {@code Deck} + */ + public SelectDeckCommand(Index targetIdx) { + requireNonNull(targetIdx); + this.targetIdx = targetIdx; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + if (model.getView() == View.PLAY) { + throw new CommandException(MESSAGE_NOT_IN_VIEW_MODE); + } + + List lastShownList = model.getFilteredDeckList(); + + if (targetIdx.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_DECK_DISPLAYED_INDEX); + } + + model.selectDeck(targetIdx); + Deck selectedDeck = model.getDeck(targetIdx); + return new CommandResult(String.format(MESSAGE_SUCCESS, selectedDeck)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SelectDeckCommand // instanceof handles nulls + && targetIdx.equals(((SelectDeckCommand) other).targetIdx)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/com/flashspeed/logic/commands/exceptions/CommandException.java similarity index 74% rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java rename to src/main/java/com/flashspeed/logic/commands/exceptions/CommandException.java index a16bd14f2cd..f4d9dc5d28f 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/com/flashspeed/logic/commands/exceptions/CommandException.java @@ -1,7 +1,7 @@ -package seedu.address.logic.commands.exceptions; +package com.flashspeed.logic.commands.exceptions; /** - * Represents an error which occurs during execution of a {@link Command}. + * Represents an error which occurs during execution of a command. */ public class CommandException extends Exception { public CommandException(String message) { diff --git a/src/main/java/com/flashspeed/logic/commands/gamecommands/AnswerNoCommand.java b/src/main/java/com/flashspeed/logic/commands/gamecommands/AnswerNoCommand.java new file mode 100644 index 00000000000..251737209e5 --- /dev/null +++ b/src/main/java/com/flashspeed/logic/commands/gamecommands/AnswerNoCommand.java @@ -0,0 +1,53 @@ +package com.flashspeed.logic.commands.gamecommands; + +import static java.util.Objects.requireNonNull; + +import com.flashspeed.logic.commands.Command; +import com.flashspeed.logic.commands.CommandResult; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.model.Model; +import com.flashspeed.model.deck.card.Card; +import com.flashspeed.model.util.View; + +/** + * Represents the command that indicates that the user answered the question incorrectly. + */ +public class AnswerNoCommand extends Command { + public static final String COMMAND_WORD = "no"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Answer no if you have the wrong answer.\n"; + public static final String MESSAGE_SUCCESS = "Oops, you got it wrong!"; + public static final String MESSAGE_NOT_PLAY_MODE = "Cannot answer card in non-play view"; + public static final String MESSAGE_NOT_FLIPPED = "Card has not flipped yet!"; + public static final String MESSAGE_END_GAME = "Session completed!"; + + /** + * Creates a AnswerNoCommand + */ + public AnswerNoCommand() { } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + if (model.getView() != View.PLAY) { + throw new CommandException(MESSAGE_NOT_PLAY_MODE); + } + if (!model.getGame().isFlipped()) { + throw new CommandException(MESSAGE_NOT_FLIPPED); + } + Card nextCard = model.answerNo(); + if (nextCard == null) { + return new CommandResult(String.format(MESSAGE_END_GAME), false, false, true, model.stop()); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AnswerNoCommand); // instanceof handles nulls + + } +} diff --git a/src/main/java/com/flashspeed/logic/commands/gamecommands/AnswerYesCommand.java b/src/main/java/com/flashspeed/logic/commands/gamecommands/AnswerYesCommand.java new file mode 100644 index 00000000000..563d0d04d83 --- /dev/null +++ b/src/main/java/com/flashspeed/logic/commands/gamecommands/AnswerYesCommand.java @@ -0,0 +1,48 @@ +package com.flashspeed.logic.commands.gamecommands; + +import static java.util.Objects.requireNonNull; + +import com.flashspeed.logic.commands.Command; +import com.flashspeed.logic.commands.CommandResult; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.model.Model; +import com.flashspeed.model.deck.card.Card; +import com.flashspeed.model.util.View; + +/** + * Represents the command that indicates that the user answered the question correctly. + */ +public class AnswerYesCommand extends Command { + public static final String COMMAND_WORD = "yes"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Answer yes if you have the correct answer.\n"; + public static final String MESSAGE_SUCCESS = "Nice, you got it right!"; + public static final String MESSAGE_NOT_PLAY_MODE = "Cannot answer card in non-play view"; + public static final String MESSAGE_NOT_FLIPPED = "Card has not flipped yet!"; + public static final String MESSAGE_END_GAME = "Session completed!"; + public AnswerYesCommand() { } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + if (model.getView() != View.PLAY) { + throw new CommandException(MESSAGE_NOT_PLAY_MODE); + } + if (!model.getGame().isFlipped()) { + throw new CommandException(MESSAGE_NOT_FLIPPED); + } + Card nextCard = model.answerYes(); + if (nextCard == null) { + return new CommandResult(String.format(MESSAGE_END_GAME), false, false, true, model.stop()); + } + return new CommandResult(String.format(MESSAGE_SUCCESS)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AnswerYesCommand); // instanceof handles nulls + + } +} diff --git a/src/main/java/com/flashspeed/logic/commands/gamecommands/FlipCommand.java b/src/main/java/com/flashspeed/logic/commands/gamecommands/FlipCommand.java new file mode 100644 index 00000000000..4331351ab45 --- /dev/null +++ b/src/main/java/com/flashspeed/logic/commands/gamecommands/FlipCommand.java @@ -0,0 +1,49 @@ +package com.flashspeed.logic.commands.gamecommands; + +import static java.util.Objects.requireNonNull; + +import com.flashspeed.logic.commands.Command; +import com.flashspeed.logic.commands.CommandResult; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.model.Model; +import com.flashspeed.model.deck.card.BackFace; +import com.flashspeed.model.util.View; + +/** + * Represents the command that informs the model manager to fiip the current flashcard. + */ +public class FlipCommand extends Command { + public static final String COMMAND_WORD = "flip"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Flips the card to see the back face.\n"; + public static final String MESSAGE_SUCCESS = "Did you get your answer right?"; + public static final String MESSAGE_NOT_PLAY_MODE = "Cannot flip card in non-play view"; + public static final String MESSAGE_ALREADY_FLIPPED = "Card already flipped!"; + private BackFace backFace; + /** + * Creates an FlipCommand. + */ + public FlipCommand() { + this.backFace = null; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + if (model.getView() != View.PLAY) { + throw new CommandException(MESSAGE_NOT_PLAY_MODE); + } + BackFace backFace = model.flip(); + if (backFace == null) { + throw new CommandException(MESSAGE_ALREADY_FLIPPED); + } + return new CommandResult(String.format(MESSAGE_SUCCESS)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FlipCommand); // instanceof handles nulls + } +} diff --git a/src/main/java/com/flashspeed/logic/commands/gamecommands/PlayCommand.java b/src/main/java/com/flashspeed/logic/commands/gamecommands/PlayCommand.java new file mode 100644 index 00000000000..0ce5070914a --- /dev/null +++ b/src/main/java/com/flashspeed/logic/commands/gamecommands/PlayCommand.java @@ -0,0 +1,63 @@ +package com.flashspeed.logic.commands.gamecommands; + +import static java.util.Objects.requireNonNull; + +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.logic.commands.Command; +import com.flashspeed.logic.commands.CommandResult; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.model.Model; +import com.flashspeed.model.deck.Deck; +import com.flashspeed.model.deck.card.Card; + +/** + * Represent the command that informs the Model Manager to start a game session. + */ +public class PlayCommand extends Command { + public static final String COMMAND_WORD = "play"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Plays a review session with a deck.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example:" + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SUCCESS = "Selected deck: %1$s"; + public static final String MESSAGE_DECK_NOT_FOUND = "Deck not found in the library!"; + public static final String MESSAGE_NO_CARD = "Oops, there are no cards in the selected deck."; + public static final String MESSAGE_ALREADY_PLAY = "You should complete or end this session " + + "before playing a new one."; + + private final Index targetIdx; + + /** + * Creates a PlayCommand with a specific {@code Deck}. + */ + public PlayCommand(Index targetIdx) { + requireNonNull(targetIdx); + this.targetIdx = targetIdx; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + Deck deck = model.getDeck(targetIdx); + if (model.getGame() != null) { + throw new CommandException(MESSAGE_ALREADY_PLAY); + } + Card card = model.play(targetIdx); + if (card == null) { + throw new CommandException(MESSAGE_DECK_NOT_FOUND); + } + if (card.getFrontFace() == null && card.getBackFace() == null) { + throw new CommandException(String.format(MESSAGE_NO_CARD)); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, deck)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PlayCommand // instanceof handles nulls + && targetIdx.equals(((PlayCommand) other).targetIdx)); + } +} diff --git a/src/main/java/com/flashspeed/logic/commands/gamecommands/StopCommand.java b/src/main/java/com/flashspeed/logic/commands/gamecommands/StopCommand.java new file mode 100644 index 00000000000..34396d68ce0 --- /dev/null +++ b/src/main/java/com/flashspeed/logic/commands/gamecommands/StopCommand.java @@ -0,0 +1,41 @@ +package com.flashspeed.logic.commands.gamecommands; + +import static java.util.Objects.requireNonNull; + +import com.flashspeed.logic.commands.Command; +import com.flashspeed.logic.commands.CommandResult; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.model.Model; +import com.flashspeed.model.util.View; + +/** + * Represents the command that stops the current game session. + */ +public class StopCommand extends Command { + public static final String COMMAND_WORD = "stop"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Stop the current game session.\n"; + public static final String MESSAGE_SUCCESS = "Session stopped!"; + public static final String MESSAGE_NOT_PLAY_MODE = "You have not started a new game session"; + /** + * Creates an StopCommand + */ + public StopCommand() { + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + if (model.getView() != View.PLAY) { + throw new CommandException(MESSAGE_NOT_PLAY_MODE); + } + return new CommandResult(String.format(MESSAGE_SUCCESS), false, false, true, model.stop()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || other instanceof StopCommand; // instanceof handles nulls + } +} diff --git a/src/main/java/com/flashspeed/logic/parser/MasterParser.java b/src/main/java/com/flashspeed/logic/parser/MasterParser.java new file mode 100644 index 00000000000..226aa3658ca --- /dev/null +++ b/src/main/java/com/flashspeed/logic/parser/MasterParser.java @@ -0,0 +1,143 @@ +package com.flashspeed.logic.parser; + +import static com.flashspeed.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static com.flashspeed.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.flashspeed.logic.commands.Command; +import com.flashspeed.logic.commands.ExitCommand; +import com.flashspeed.logic.commands.HelpCommand; +import com.flashspeed.logic.commands.ResetLibraryCommand; +import com.flashspeed.logic.commands.ReturnToLibraryCommand; +import com.flashspeed.logic.commands.cardcommands.AddCardCommand; +import com.flashspeed.logic.commands.cardcommands.DeleteCardCommand; +import com.flashspeed.logic.commands.cardcommands.EditCardCommand; +import com.flashspeed.logic.commands.deckcommands.CreateDeckCommand; +import com.flashspeed.logic.commands.deckcommands.RemoveDeckCommand; +import com.flashspeed.logic.commands.deckcommands.RenameDeckCommand; +import com.flashspeed.logic.commands.deckcommands.SelectDeckCommand; + +import com.flashspeed.logic.commands.gamecommands.AnswerNoCommand; +import com.flashspeed.logic.commands.gamecommands.AnswerYesCommand; +import com.flashspeed.logic.commands.gamecommands.FlipCommand; +import com.flashspeed.logic.commands.gamecommands.PlayCommand; +import com.flashspeed.logic.commands.gamecommands.StopCommand; +import com.flashspeed.logic.parser.cardparsers.AddCardCommandParser; +import com.flashspeed.logic.parser.cardparsers.DeleteCardCommandParser; +import com.flashspeed.logic.parser.cardparsers.EditCardCommandParser; +import com.flashspeed.logic.parser.deckparsers.CreateDeckCommandParser; +import com.flashspeed.logic.parser.deckparsers.RemoveDeckCommandParser; +import com.flashspeed.logic.parser.deckparsers.RenameDeckCommandParser; +import com.flashspeed.logic.parser.deckparsers.SelectDeckCommandParser; +import com.flashspeed.logic.parser.exceptions.ParseException; +import com.flashspeed.logic.parser.gameparsers.PlayCommandParser; + +/** + * Parses input for FlashSpeed. + */ +public class MasterParser { + + /** + * 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)); + } + + // to handle get of commandWord and args + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + + /* List of commands: + Deck: + - CreateDeck + - RemoveDeck + - RenameDeck + - SelectDeck + Card: + - AddCard + - DeleteCard + - EditCard + Game: + - Play + - Flip + - AnswerYes + - AnswerNO + - Stop + General: + - Help + - Exit + - ReturnToLibrary + + */ + switch (commandWord) { + + // Deck functions + case CreateDeckCommand.COMMAND_WORD: + return new CreateDeckCommandParser().parse(arguments); + + case RemoveDeckCommand.COMMAND_WORD: + return new RemoveDeckCommandParser().parse(arguments); + + case RenameDeckCommand.COMMAND_WORD: + return new RenameDeckCommandParser().parse(arguments); + + case SelectDeckCommand.COMMAND_WORD: + return new SelectDeckCommandParser().parse(arguments); + + // Card functions + case AddCardCommand.COMMAND_WORD: + return new AddCardCommandParser().parse(arguments); + + case DeleteCardCommand.COMMAND_WORD: + return new DeleteCardCommandParser().parse(arguments); + + case EditCardCommand.COMMAND_WORD: + return new EditCardCommandParser().parse(arguments); + + // Game functions + case PlayCommand.COMMAND_WORD: + return new PlayCommandParser().parse(arguments); + + case FlipCommand.COMMAND_WORD: + return new FlipCommand(); + + case AnswerYesCommand.COMMAND_WORD: + return new AnswerYesCommand(); + + case AnswerNoCommand.COMMAND_WORD: + return new AnswerNoCommand(); + case StopCommand.COMMAND_WORD: + return new StopCommand(); + + // General + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + case ResetLibraryCommand.COMMAND_WORD: + return new ResetLibraryCommand(); + + case ReturnToLibraryCommand.COMMAND_WORD: + return new ReturnToLibraryCommand(); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/com/flashspeed/logic/parser/Parser.java similarity index 72% rename from src/main/java/seedu/address/logic/parser/Parser.java rename to src/main/java/com/flashspeed/logic/parser/Parser.java index d6551ad8e3f..cbb19160397 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/com/flashspeed/logic/parser/Parser.java @@ -1,7 +1,7 @@ -package seedu.address.logic.parser; +package com.flashspeed.logic.parser; -import seedu.address.logic.commands.Command; -import seedu.address.logic.parser.exceptions.ParseException; +import com.flashspeed.logic.commands.Command; +import com.flashspeed.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/com/flashspeed/logic/parser/ParserUtil.java b/src/main/java/com/flashspeed/logic/parser/ParserUtil.java new file mode 100644 index 00000000000..fc8ad893708 --- /dev/null +++ b/src/main/java/com/flashspeed/logic/parser/ParserUtil.java @@ -0,0 +1,46 @@ +package com.flashspeed.logic.parser; + +import static java.util.Objects.requireNonNull; + +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.commons.util.StringUtil; +import com.flashspeed.logic.parser.exceptions.ParseException; +import com.flashspeed.model.deck.Name; + +/** + * 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 strippedName = name.strip(); + if (!Name.isValidName(strippedName)) { + throw new ParseException(Name.MESSAGE_CONSTRAINTS); + } + + return new Name(strippedName); + } +} diff --git a/src/main/java/com/flashspeed/logic/parser/cardparsers/AddCardCommandParser.java b/src/main/java/com/flashspeed/logic/parser/cardparsers/AddCardCommandParser.java new file mode 100644 index 00000000000..fa72ee67b59 --- /dev/null +++ b/src/main/java/com/flashspeed/logic/parser/cardparsers/AddCardCommandParser.java @@ -0,0 +1,59 @@ +package com.flashspeed.logic.parser.cardparsers; + +import static com.flashspeed.commons.core.Messages.MESSAGE_INPUT_CONTAINS_COLON; +import static com.flashspeed.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.flashspeed.logic.commands.cardcommands.AddCardCommand; +import com.flashspeed.logic.commands.cardcommands.EditCardCommand; +import com.flashspeed.logic.parser.Parser; +import com.flashspeed.logic.parser.exceptions.ParseException; +import com.flashspeed.model.deck.card.BackFace; +import com.flashspeed.model.deck.card.Card; +import com.flashspeed.model.deck.card.FrontFace; + +/** + * Parses input arguments and creates a new AddCardCommand object. + */ +public class AddCardCommandParser implements Parser { + + /** + * Gets pattern FRONT:BACK, spaces before and after ":" are handled. + */ + private static final Pattern COMMAND_FORMAT = Pattern.compile( + "(?.*)" + + "(\\s*[\u003a\u02d0\u02d1\u02f8\u05c3\u2236\u2360\ua789\ufe13\uff1a\ufe55]\\s*)" + + "(?.*)"); + + /** + * Parses the given {@code String} of arguments in the context of the AddCardCommand + * and returns an AddCardCommand object for execution. + * + * @throws ParseException if the user input does not conform to the expected format + */ + public AddCardCommand parse(String args) throws ParseException { + final Matcher matcher = COMMAND_FORMAT.matcher(args.strip()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCardCommand.MESSAGE_USAGE)); + } + + final String frontValue = matcher.group("front"); + final String backValue = matcher.group("back"); + + if (frontValue.isBlank() || backValue.isBlank()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCardCommand.MESSAGE_USAGE)); + } + + if (frontValue.contains(":") || backValue.contains(":")) { + throw new ParseException(String.format(MESSAGE_INPUT_CONTAINS_COLON, EditCardCommand.MESSAGE_USAGE)); + } + + FrontFace front = new FrontFace(frontValue); + BackFace back = new BackFace(backValue); + + Card card = new Card(front, back); + return new AddCardCommand(card); + } +} diff --git a/src/main/java/com/flashspeed/logic/parser/cardparsers/DeleteCardCommandParser.java b/src/main/java/com/flashspeed/logic/parser/cardparsers/DeleteCardCommandParser.java new file mode 100644 index 00000000000..0c69abc1ab3 --- /dev/null +++ b/src/main/java/com/flashspeed/logic/parser/cardparsers/DeleteCardCommandParser.java @@ -0,0 +1,30 @@ +package com.flashspeed.logic.parser.cardparsers; + +import static com.flashspeed.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.logic.commands.cardcommands.DeleteCardCommand; +import com.flashspeed.logic.parser.Parser; +import com.flashspeed.logic.parser.ParserUtil; +import com.flashspeed.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteCardCommand object. + */ +public class DeleteCardCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteCardCommand + * and returns a DeleteCardCommand object for execution. + * @throws ParseException if the user input does not conform to the expected format + */ + public DeleteCardCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteCardCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCardCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/com/flashspeed/logic/parser/cardparsers/EditCardCommandParser.java b/src/main/java/com/flashspeed/logic/parser/cardparsers/EditCardCommandParser.java new file mode 100644 index 00000000000..952a75c1fdd --- /dev/null +++ b/src/main/java/com/flashspeed/logic/parser/cardparsers/EditCardCommandParser.java @@ -0,0 +1,64 @@ +package com.flashspeed.logic.parser.cardparsers; + +import static com.flashspeed.commons.core.Messages.MESSAGE_INPUT_CONTAINS_COLON; +import static com.flashspeed.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.logic.commands.cardcommands.EditCardCommand; +import com.flashspeed.logic.parser.Parser; +import com.flashspeed.logic.parser.ParserUtil; +import com.flashspeed.logic.parser.exceptions.ParseException; +import com.flashspeed.model.deck.card.BackFace; +import com.flashspeed.model.deck.card.FrontFace; + +/** + * Parses input arguments and creates a new EditCardCommand object. + */ +public class EditCardCommandParser implements Parser { + + /** + * Gets pattern INDEX FRONT:BACK, spaces before and after ":" is handled. + * Also, can choose whether to allow no space between INDEX and FRONT:BACK. + * Allows for fast edit (INDEX :BACK) or (INDEX FRONT:). + */ + private static final Pattern COMMAND_FORMAT = Pattern.compile("(?\\d+)(\\s+)(?.*)" + + "(\\s*[\u003a\u02d0\u02d1\u02f8\u05c3\u2236\u2360\ua789\ufe13\uff1a\ufe55]\\s*)" + + "(?.*)"); + + /** + * Parses the given {@code String} of arguments in the context of the DeleteCardCommand + * and returns a DeleteCardCommand object for execution. + * @throws ParseException if the user input does not conform to the expected format + */ + public EditCardCommand parse(String args) throws ParseException { + final Matcher matcher = COMMAND_FORMAT.matcher(args.strip()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCardCommand.MESSAGE_USAGE)); + } + + final String indexStr = matcher.group("index"); + final String frontValue = matcher.group("front"); + final String backValue = matcher.group("back"); + + if (frontValue.isBlank() && backValue.isBlank()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCardCommand.MESSAGE_USAGE)); + } + + if (frontValue.contains(":") || backValue.contains(":")) { + throw new ParseException(String.format(MESSAGE_INPUT_CONTAINS_COLON, EditCardCommand.MESSAGE_USAGE)); + } + + FrontFace front = new FrontFace(frontValue); + BackFace back = new BackFace(backValue); + + try { + Index index = ParserUtil.parseIndex(indexStr); + return new EditCardCommand(index, front, back); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCardCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/com/flashspeed/logic/parser/deckparsers/CreateDeckCommandParser.java b/src/main/java/com/flashspeed/logic/parser/deckparsers/CreateDeckCommandParser.java new file mode 100644 index 00000000000..330abb5b4e7 --- /dev/null +++ b/src/main/java/com/flashspeed/logic/parser/deckparsers/CreateDeckCommandParser.java @@ -0,0 +1,34 @@ +package com.flashspeed.logic.parser.deckparsers; + +import static com.flashspeed.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import com.flashspeed.logic.commands.deckcommands.CreateDeckCommand; +import com.flashspeed.logic.parser.Parser; +import com.flashspeed.logic.parser.ParserUtil; +import com.flashspeed.logic.parser.exceptions.ParseException; +import com.flashspeed.model.deck.Deck; +import com.flashspeed.model.deck.Name; + +/** + * Parses input arguments and creates a new CreateDeckCommand object. + */ +public class CreateDeckCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CreateDeckCommand + * and returns an CreateDeckCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CreateDeckCommand parse(String args) throws ParseException { + String strippedArgs = args.strip(); + if (strippedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, CreateDeckCommand.MESSAGE_USAGE)); + } + + Name name = ParserUtil.parseName(strippedArgs); + + Deck deck = new Deck(name); + return new CreateDeckCommand(deck); + } +} diff --git a/src/main/java/com/flashspeed/logic/parser/deckparsers/RemoveDeckCommandParser.java b/src/main/java/com/flashspeed/logic/parser/deckparsers/RemoveDeckCommandParser.java new file mode 100644 index 00000000000..ffd70ef5d54 --- /dev/null +++ b/src/main/java/com/flashspeed/logic/parser/deckparsers/RemoveDeckCommandParser.java @@ -0,0 +1,30 @@ +package com.flashspeed.logic.parser.deckparsers; + +import static com.flashspeed.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.logic.commands.deckcommands.RemoveDeckCommand; +import com.flashspeed.logic.parser.Parser; +import com.flashspeed.logic.parser.ParserUtil; +import com.flashspeed.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new RemoveDeckCommand object + */ +public class RemoveDeckCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the RemoveDeckCommand + * and returns an RemoveDeckCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RemoveDeckCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new RemoveDeckCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveDeckCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/com/flashspeed/logic/parser/deckparsers/RenameDeckCommandParser.java b/src/main/java/com/flashspeed/logic/parser/deckparsers/RenameDeckCommandParser.java new file mode 100644 index 00000000000..f4ddc6b0518 --- /dev/null +++ b/src/main/java/com/flashspeed/logic/parser/deckparsers/RenameDeckCommandParser.java @@ -0,0 +1,56 @@ +package com.flashspeed.logic.parser.deckparsers; + +import static com.flashspeed.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static java.util.Objects.requireNonNull; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.logic.commands.deckcommands.RenameDeckCommand; +import com.flashspeed.logic.parser.Parser; +import com.flashspeed.logic.parser.ParserUtil; +import com.flashspeed.logic.parser.exceptions.ParseException; +import com.flashspeed.model.deck.Name; + +/** + * Parses input arguments and creates a new RenameDeckCommand object + */ +public class RenameDeckCommandParser implements Parser { + + /** + * Gets pattern INDEX NAME, spaces between INDEX and NAME is handled. + */ + private static final Pattern COMMAND_FORMAT = Pattern.compile("(?\\d+)(\\s+)(?.*)"); + + /** + * Parses the given {@code String} of arguments in the context of the RenameDeckCommand + * and returns an RenameDeckCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RenameDeckCommand parse(String args) throws ParseException { + requireNonNull(args); + + final Matcher matcher = COMMAND_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RenameDeckCommand.MESSAGE_USAGE)); + } + + final String indexStr = matcher.group("index"); + final String newName = matcher.group("name"); + + if (newName.isBlank()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RenameDeckCommand.MESSAGE_USAGE)); + } + + Name name = new Name(newName); + + try { + Index index = ParserUtil.parseIndex(indexStr); + return new RenameDeckCommand(index, name); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, RenameDeckCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/com/flashspeed/logic/parser/deckparsers/SelectDeckCommandParser.java b/src/main/java/com/flashspeed/logic/parser/deckparsers/SelectDeckCommandParser.java new file mode 100644 index 00000000000..481d013161d --- /dev/null +++ b/src/main/java/com/flashspeed/logic/parser/deckparsers/SelectDeckCommandParser.java @@ -0,0 +1,30 @@ +package com.flashspeed.logic.parser.deckparsers; + +import static com.flashspeed.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.logic.commands.deckcommands.SelectDeckCommand; +import com.flashspeed.logic.parser.Parser; +import com.flashspeed.logic.parser.ParserUtil; +import com.flashspeed.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SelectCommand object + */ +public class SelectDeckCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the SelectDeckCommand + * and returns an SelectDeckCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SelectDeckCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new SelectDeckCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectDeckCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/com/flashspeed/logic/parser/exceptions/ParseException.java similarity index 72% rename from src/main/java/seedu/address/logic/parser/exceptions/ParseException.java rename to src/main/java/com/flashspeed/logic/parser/exceptions/ParseException.java index 158a1a54c1c..4de5ec68c02 100644 --- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java +++ b/src/main/java/com/flashspeed/logic/parser/exceptions/ParseException.java @@ -1,6 +1,6 @@ -package seedu.address.logic.parser.exceptions; +package com.flashspeed.logic.parser.exceptions; -import seedu.address.commons.exceptions.IllegalValueException; +import com.flashspeed.commons.exceptions.IllegalValueException; /** * Represents a parse error encountered by a parser. diff --git a/src/main/java/com/flashspeed/logic/parser/gameparsers/PlayCommandParser.java b/src/main/java/com/flashspeed/logic/parser/gameparsers/PlayCommandParser.java new file mode 100644 index 00000000000..bf5d273247d --- /dev/null +++ b/src/main/java/com/flashspeed/logic/parser/gameparsers/PlayCommandParser.java @@ -0,0 +1,30 @@ +package com.flashspeed.logic.parser.gameparsers; + +import static com.flashspeed.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.logic.commands.gamecommands.PlayCommand; +import com.flashspeed.logic.parser.Parser; +import com.flashspeed.logic.parser.ParserUtil; +import com.flashspeed.logic.parser.exceptions.ParseException; + +/** + * 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 { + try { + Index index = ParserUtil.parseIndex(args); + return new PlayCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, PlayCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/com/flashspeed/model/Game.java b/src/main/java/com/flashspeed/model/Game.java new file mode 100644 index 00000000000..d5245ff99c2 --- /dev/null +++ b/src/main/java/com/flashspeed/model/Game.java @@ -0,0 +1,63 @@ +package com.flashspeed.model; + +import com.flashspeed.model.deck.card.BackFace; +import com.flashspeed.model.deck.card.Card; + +import javafx.collections.ObservableList; + +/** + * The API of Game Component + */ +public interface Game { + + /** + * Returns the value of whether a card has been flipped. + * @return true if a card has been flipped, false otherwise. + */ + boolean isFlipped(); + + /** + * Flips the card to the back face. + * @return true if the card has not been flipped, false otherwise + */ + BackFace flip(); + + /** + * Returns a random next card after user answers Yes. + * Removes the correct card from the deck. + * @return the next card or null if card list is empty + */ + Card answerYes(); + + /** + * Returns the next card after user answers No. + * Adds a duplicate wrong card to the deck. + * @return the next card or null if card list is empty + */ + Card answerNo(); + + /** + * Returns the statistics report when user stops the sessions. + */ + Statistics stop(); + + /** + * Returns the current index of the card. + */ + int getCurrCardIdx(); + + /** + * Returns the number of cards have been attempted. + */ + int getCardAttempted(); + + /** + * Returns number of the remaining cards inside the deck. + */ + int getDeckSize(); + + /** + * Returns the card list. + */ + ObservableList getCards(); +} diff --git a/src/main/java/com/flashspeed/model/GameManager.java b/src/main/java/com/flashspeed/model/GameManager.java new file mode 100644 index 00000000000..eed1f3ee535 --- /dev/null +++ b/src/main/java/com/flashspeed/model/GameManager.java @@ -0,0 +1,136 @@ +package com.flashspeed.model; + +import java.util.Random; + +import com.flashspeed.model.deck.Deck; +import com.flashspeed.model.deck.card.BackFace; +import com.flashspeed.model.deck.card.Card; + +import javafx.collections.ObservableList; + +/** + * Represents the model of the game session. + */ +public class GameManager implements Game { + private Random randGen; + private boolean flipped; + private ObservableList cards; + private Statistics statistics; + private int deckSize; + private int currCardIdx; + private int cardAttempted; + + + /** + * Initializes a GameManager with the given deck. + */ + public GameManager(Deck deck) { + randGen = new Random(); + this.flipped = false; + this.cards = deck.asObservableList(); + this.statistics = new Statistics(cards); + this.deckSize = this.cards.size(); + this.currCardIdx = randGen.nextInt(this.deckSize); + this.cardAttempted = 0; + } + + /** + * Returns the value of whether a card has been flipped. + * @return true if a card has been flipped, false otherwise. + */ + @Override + public boolean isFlipped() { + return this.flipped; + } + + /** + * Flips the card to the back face. + * @return true if the card has not been flipped, false otherwise + */ + @Override + public BackFace flip() { + if (flipped) { + return null; + } + flipped = true; + return cards.get(currCardIdx).getBackFace(); + } + + /** + * Returns a random next card after user answers Yes. + * Removes the correct card from the deck. + * @return the next card or null if card list is empty + */ + @Override + public Card answerYes() { + statistics.incrementCorrectAttempt(cards.get(currCardIdx)); + cards.remove(currCardIdx); + this.deckSize -= 1; + flipped = false; + if (this.deckSize == 0) { + return null; + } + this.currCardIdx = randGen.nextInt(this.deckSize); + cardAttempted++; + return cards.get(currCardIdx); + } + + /** + * Returns the next card after user answers No. + * Adds a duplicate wrong card to the deck. + * @return the next card or null if card list is empty + */ + @Override + public Card answerNo() { + boolean hasTwoCards = statistics.incrementWrongAttempt(cards.get(currCardIdx)); + if (!hasTwoCards) { + Card currCard = cards.get(currCardIdx); + cards.add(currCard); + this.deckSize += 1; + } + flipped = false; + this.currCardIdx = randGen.nextInt(this.deckSize); + cardAttempted++; + return cards.get(currCardIdx); + } + + /** + * Returns the statistics report when user stops the sessions. + */ + @Override + public Statistics stop() { + return this.statistics; + } + + /** + * Returns the current index of the card. + */ + @Override + public int getCurrCardIdx() { + return this.currCardIdx; + } + + /** + * Returns the number of cards have been attempted. + */ + @Override + public int getCardAttempted() { + return cardAttempted; + } + + /** + * Returns number of the remaining cards inside the deck. + */ + @Override + public int getDeckSize() { + return deckSize; + } + + /** + * Returns the card list. + */ + @Override + public ObservableList getCards() { + return this.cards; + } +} diff --git a/src/main/java/com/flashspeed/model/Library.java b/src/main/java/com/flashspeed/model/Library.java new file mode 100644 index 00000000000..ed1fafd8c02 --- /dev/null +++ b/src/main/java/com/flashspeed/model/Library.java @@ -0,0 +1,135 @@ +package com.flashspeed.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.model.deck.Deck; +import com.flashspeed.model.deck.UniqueDeckList; + +import javafx.collections.ObservableList; + +/** + * Wraps all data at the library level. + * Duplicates are not allowed (by .isSameDeck comparison). + */ +public class Library implements ReadOnlyLibrary { + + private final UniqueDeckList decks; + + /* + * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication + * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html + * + * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication + * among constructors. + */ + { + decks = new UniqueDeckList(); + } + + public Library() {} + + /** + * Creates an Library using the Decks in {@code toBeCopied}. + */ + public Library(ReadOnlyLibrary toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// list overwrite operations + + /** + * Replaces the contents of the deck list with {@code decks}. + * {@code decks} must not contain duplicate decks. + */ + public void setDecks(List decks) { + this.decks.setDecks(decks); + } + + + /** + * Resets the existing data of this {@code Library} with {@code newData}. + */ + public void resetData(ReadOnlyLibrary newData) { + requireNonNull(newData); + setDecks(newData.getDeckList()); + } + + //// deck-level operations + + /** + * Returns true if a deck with the same identity as {@code deck} exists in the Library. + */ + public boolean hasDeck(Deck deck) { + requireNonNull(deck); + return decks.contains(deck); + } + + public Deck getDeck(Index index) { + int idxInInt = index.getZeroBased(); + return decks.get(idxInInt); + + } + + /** + * Adds a deck to the library. + * The deck must not already exist in the library. + */ + public void addDeck(Deck p) { + decks.add(p); + } + + public void createDeck(Deck p) { + decks.add(p); + } + + /** + * Replaces the given deck {@code target} in the list with {@code editedDeck}. + * {@code target} must exist in the library. + * The deck identity of {@code editedDeck} must not be the same as another existing deck in the library. + */ + public void setDeck(Deck target, Deck editedDeck) { + requireNonNull(editedDeck); + + decks.setDeck(target, editedDeck); + } + + /** + * Removes {@code key} from this {@code Library}. + * {@code key} must exist in the library . + */ + + public void deleteDeck(Deck key) { + decks.remove(key); + } + + // TODO: refine + @Override + public String toString() { + return decks.asUnmodifiableObservableList().size() + " decks"; + } + + @Override + public ObservableList getDeckList() { // remove the code at the end + return decks.asUnmodifiableObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Library // instanceof handles nulls + && decks.equals(((Library) other).decks)); + } + + @Override + public int hashCode() { + return decks.hashCode(); + } + + public int getSize() { + return decks.getSize(); + } +} diff --git a/src/main/java/com/flashspeed/model/Model.java b/src/main/java/com/flashspeed/model/Model.java new file mode 100644 index 00000000000..2cef99359f0 --- /dev/null +++ b/src/main/java/com/flashspeed/model/Model.java @@ -0,0 +1,266 @@ +package com.flashspeed.model; + +import java.nio.file.Path; +import java.util.function.Predicate; + +import com.flashspeed.commons.core.GuiSettings; +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.model.deck.Deck; +import com.flashspeed.model.deck.Name; +import com.flashspeed.model.deck.card.BackFace; +import com.flashspeed.model.deck.card.Card; +import com.flashspeed.model.util.View; + +import javafx.beans.property.ReadOnlyProperty; +import javafx.collections.ObservableList; + +/** + * The API of the Model component. + */ +public interface Model { + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_DECKS = unused -> true; + + /** + * Returns the user prefs. + */ + ReadOnlyUserPrefs getUserPrefs(); + + /** + * Replaces user prefs data with the data in {@code userPrefs}. + */ + void setUserPrefs(ReadOnlyUserPrefs userPrefs); + + /** + * Returns the user prefs' GUI settings. + */ + GuiSettings getGuiSettings(); + + /** + * Sets the user prefs' GUI settings. + */ + void setGuiSettings(GuiSettings guiSettings); + + /** + * Returns the user prefs' library file path. + */ + Path getLibraryFilePath(); + + /** + * Sets the user prefs' library file path. + */ + void setLibraryFilePath(Path libraryFilePath); + + /** + * Replaces library data with the data in {@code library}. + */ + void setLibrary(ReadOnlyLibrary library); + + /** + * Returns the library + * */ + ReadOnlyLibrary getLibrary(); + + /** + * Returns true if a deck with the same identity as {@code deck} exists in the library. + */ + boolean hasDeck(Deck deck); + + /** + * Deletes the given deck. + * The deck must exist in the library. + */ + void deleteDeck(Deck target); + + /** + * Adds the given deck. + * {@code deck} must not already exist in the library. + */ + void createDeck(Deck deck); + + /** + * Selects a deck. + */ + void selectDeck(Index targetIdx); + + /** + * Returns the current deck; + */ + Deck getCurrentDeck(); + + /** + * Returns the deck at index of library; + */ + Deck getDeck(Index index); + + /** Renames the deck at index in library. + * @return true if there is no deck with the same name, false otherwise + */ + boolean renameDeck(Index targetIndex, Name name); + + /** + * Brings the user from deck view to library view. + */ + void returnToLibrary(); + + /** + * Checks if a card with the same identity as {@code card} exists in the deck. + * @return true if {@code card} exists in the deck + */ + boolean hasCard(Card card); + + /** + * Deletes the given card. + * {@code target} must exist in the deck. + */ + void deleteCard(Card target); + + /** + * Adds the given card to the deck. + * {@code card} must not already exist in the deck. + */ + void addCard(Card card); + + /** + * Replaces the given old card with the new card. + * {@code target} must exist in the deck. + * {@code card} must not already exist in the deck. + */ + void replaceCard(Card target, Card card); + + /** + * Gets the current view of the model. + * @return The current view of the model. + */ + View getView(); + + /** + * Replaces the given deck {@code target} with {@code editedDeck}. + * {@code target} must exist in the library. + * The deck identity of {@code editedDeck} must not be the same as another existing deck in the library. + */ + void setDeck(Deck target, Deck editedDeck); + + /** Returns an unmodifiable view of the filtered deck list. */ + ObservableList getFilteredDeckList(); + + /** Returns an unmodifiable view of the filtered card list. */ + ObservableList getFilteredCardList(); + + /** + * Updates the filter of the filtered deck list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredDeckList(Predicate predicate); + + + /** + * Returns the card with the given index. + */ + Card getCard(Index index); + + /** + * Starts a game session with a given deck index. + * @param index index of the deck to play with. + * @return a randomly selected card from the deck + */ + Card play(Index index); + + //=========== Play View ====================================================================== + /** + * Flips the card to the back face. + * @return true if the card has not been flipped, false otherwise + */ + BackFace flip(); + + /** + * Returns the next card after user answers Yes. + * @return the next card or null if card list is empty + */ + Card answerYes(); + + /** + * Returns the next card after user answers No. + * @return the next card or null if card list is empty + */ + Card answerNo(); + + /** + * Returns the game manager object. + */ + GameManager getGame(); + + /** + * Stops the game session. + * @return the statistics report. + */ + Statistics stop(); + + + //=========== SimpleObjectProperty ====================================================================== + + /** + * Return selected Deck Read-only Property + */ + ReadOnlyProperty selectedDeckProperty(); + + /** + * Return the current View Read-only Property + */ + ReadOnlyProperty currentViewProperty(); + + /** + * Return the Card that is been playing Read-only Property + */ + ReadOnlyProperty playingCardProperty(); + + /** + * Return the Flipped status Read-only Property + */ + ReadOnlyProperty flippedProperty(); + + /** + * Return the no of Cards attempted Read-only Property + */ + ReadOnlyProperty cardAttemptedProperty(); + + /** + * Return the no of Cards remaining Read-only Property + */ + ReadOnlyProperty cardRemainingProperty(); + + /** + * * Sets the selected deck Read-only Property + */ + void setSelectedDeck(Deck deck); + + /** + * Sets the value of currentView in Read-only Property + * Toggle currentView between LIBRARY and PLAY only to switch between CardListPanel and PlayPanel + */ + void setCurrentView(View view); + + /** + * Sets the playing card Read-only property + */ + void setPlayingCard(Card card); + + /** + * Sets the value of flipped Read-only Property + */ + void setFlipped(Boolean value); + + + /** + * Sets the number of cards attempted Read-only Property + */ + void setCardAttempted(int value); + + /** + * Sets the number of remaining cards Read-only Property + */ + void setCardRemaining(int value); + + + +} diff --git a/src/main/java/com/flashspeed/model/ModelManager.java b/src/main/java/com/flashspeed/model/ModelManager.java new file mode 100644 index 00000000000..03d2d6369b3 --- /dev/null +++ b/src/main/java/com/flashspeed/model/ModelManager.java @@ -0,0 +1,587 @@ +package com.flashspeed.model; + +import static com.flashspeed.commons.util.CollectionUtil.requireAllNonNull; +import static java.util.Objects.requireNonNull; + +import java.nio.file.Path; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.logging.Logger; + +import com.flashspeed.commons.core.GuiSettings; +import com.flashspeed.commons.core.LogsCenter; +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.model.deck.Deck; +import com.flashspeed.model.deck.Name; +import com.flashspeed.model.deck.card.BackFace; +import com.flashspeed.model.deck.card.Card; +import com.flashspeed.model.util.View; + +import javafx.beans.property.ReadOnlyProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; + +/** + * Represents the in-memory model of the library data. + */ +public class ModelManager implements Model { + private static final Logger logger = LogsCenter.getLogger(ModelManager.class); + + private final Library library; + private final UserPrefs userPrefs; + private final FilteredList filteredDecks; + + private View view; + + private Optional deckIndex; + + //Implement objects as SimpleObjectProperty to work with UI + private final SimpleObjectProperty selectedDeck = new SimpleObjectProperty<>(); + private final SimpleObjectProperty currentView = new SimpleObjectProperty<>(); + private final SimpleObjectProperty playingCard = new SimpleObjectProperty<>(); + private final SimpleObjectProperty flipped = new SimpleObjectProperty<>(); + private final SimpleObjectProperty cardAttempted = new SimpleObjectProperty<>(); + private final SimpleObjectProperty cardRemaining = new SimpleObjectProperty<>(); + private GameManager game; + + /** + * Initializes a ModelManager with the given library and userPrefs. + */ + public ModelManager(ReadOnlyLibrary library, ReadOnlyUserPrefs userPrefs) { + super(); + requireAllNonNull(library, userPrefs); + + logger.fine("Initializing with library: " + library + " and user prefs " + userPrefs); + + this.library = new Library(library); + this.userPrefs = new UserPrefs(userPrefs); + filteredDecks = new FilteredList<>(this.library.getDeckList()); + this.deckIndex = Optional.empty(); + this.view = View.LIBRARY; // 1st view will always be in library + setCurrentView(View.LIBRARY); + this.game = null; + } + + /** + * Creates a model manager. + */ + public ModelManager() { + this(new Library(), new UserPrefs()); + } + + /** + * Gets the current view of the model. + * @return The current view of the model. + */ + public View getView() { + return view; + } + + /** + * Returns the index of the selected deck. + */ + public Optional getDeckIndex() { + return this.deckIndex; + } + + //=========== UserPrefs ================================================================================== + + /** + * Returns the user prefs. + */ + @Override + public ReadOnlyUserPrefs getUserPrefs() { + return userPrefs; + } + + /** + * Replaces user prefs data with the data in {@code userPrefs}. + */ + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + requireNonNull(userPrefs); + this.userPrefs.resetData(userPrefs); + } + + /** + * Returns the user prefs' GUI settings. + */ + @Override + public GuiSettings getGuiSettings() { + return userPrefs.getGuiSettings(); + } + + /** + * Sets the user prefs' GUI settings. + */ + @Override + public void setGuiSettings(GuiSettings guiSettings) { + requireNonNull(guiSettings); + userPrefs.setGuiSettings(guiSettings); + } + + /** + * Returns the user prefs' library file path. + */ + @Override + public Path getLibraryFilePath() { + return userPrefs.getLibraryFilePath(); + } + + /** + * Sets the user prefs' library file path. + */ + @Override + public void setLibraryFilePath(Path libraryFilePath) { + requireNonNull(libraryFilePath); + userPrefs.setLibraryFilePath(libraryFilePath); + } + + + //=========== Library ================================================================================ + + /** + * Replaces library data with the data in {@code library}. + */ + @Override + public void setLibrary(ReadOnlyLibrary library) { + this.library.resetData(library); + } + + /** + * Returns the library + * */ + @Override + public ReadOnlyLibrary getLibrary() { + return library; + } + + /** + * Returns true if a deck with the same identity as {@code deck} exists in the library. + */ + @Override + public boolean hasDeck(Deck deck) { + requireNonNull(deck); + return library.hasDeck(deck); + } + + /** + * Deletes the given deck. + * The deck must exist in the library. + */ + @Override + public void deleteDeck(Deck target) { + library.deleteDeck(target); + if (selectedDeck != null && selectedDeck.getValue() != target) { + Deck deck = selectedDeck.getValue(); + returnToLibrary(); + setSelectedDeck(deck); + } else { + returnToLibrary(); + } + + + } + + /** + * Adds the given deck. + * {@code deck} must not already exist in the library. + */ + @Override + public void createDeck(Deck deck) { + library.createDeck(deck); + Index currIndex = Index.fromZeroBased(library.getDeckList().indexOf(deck)); + selectDeck(currIndex); + //setSelectedDeck(deck); + } + + /** + * Selects a deck. + */ + @Override + public void selectDeck(Index targetIdx) { + deckIndex = Optional.of(targetIdx); + this.view = View.DECK; + setSelectedDeck(library.getDeck(targetIdx)); + } + + /** Renames the deck at index in library. + * @return true if there is no deck with the same name, false otherwise + */ + @Override + public boolean renameDeck(Index targetIndex, Name name) { + Deck deck = library.getDeck(targetIndex); + Deck temp = new Deck(name); + if (library.hasDeck(temp)) { + return false; + } else { + deck.setName(name); + returnToLibrary(); + selectDeck(targetIndex); + //setSelectedDeck(deck); + return true; + } + } + + /** + * Returns the current deck; + */ + @Override + public Deck getCurrentDeck() { + if (deckIndex.equals(Optional.empty())) { + return null; + } + Deck deck = library.getDeck(deckIndex.get()); + setSelectedDeck(deck); + return deck; + } + + /** + * Returns the deck at index of library; + */ + @Override + public Deck getDeck(Index targetIdx) { + return library.getDeck(targetIdx); + } + + /** + * Brings the user from deck view to library view. + */ + @Override + public void returnToLibrary() { + selectedDeck.setValue(null); + deckIndex = Optional.empty(); + this.view = View.LIBRARY; + } + + /** + * Checks if a card with the same identity as {@code card} exists in the deck. + * @return true if {@code card} exists in the deck + */ + @Override + public boolean hasCard(Card card) { + Deck deck = library.getDeck(deckIndex.get()); + if (deck == null) { + return false; + } + requireNonNull(card); + return deck.contains(card); + } + + /** + * Returns the card with the given index. + */ + @Override + public Card getCard(Index index) { + return library.getDeck(deckIndex.get()).getCard(index); + } + + /** + * Deletes the given card. + * {@code target} must exist in the deck. + */ + @Override + public void deleteCard(Card cardToDelete) { + Deck deck = library.getDeck(deckIndex.get()); + if (deck == null) { + return; + } + deck.remove(cardToDelete); + returnToLibrary(); + setSelectedDeck(deck); + } + + /** + * Adds the given card to the deck. + * {@code card} must not already exist in the deck. + */ + @Override + public void addCard(Card card) { + Deck deck = library.getDeck(deckIndex.get()); + if (deck == null) { + return; + } + deck.add(card); + returnToLibrary(); + setSelectedDeck(deck); + } + + /** + * Replaces the given old card with the new card. + * {@code target} must exist in the deck. + * {@code card} must not already exist in the deck. + */ + @Override + public void replaceCard(Card target, Card card) { + Deck deck = library.getDeck(deckIndex.get()); + if (deck == null) { + return; + } + deck.replace(target, card); + returnToLibrary(); + setSelectedDeck(deck); + } + + /** + * Replaces the given deck {@code target} with {@code editedDeck}. + * {@code target} must exist in the library. + * The deck identity of {@code editedDeck} must not be the same as another existing deck in the library. + */ + @Override + public void setDeck(Deck target, Deck editedDeck) { + requireAllNonNull(target, editedDeck); + library.setDeck(target, editedDeck); + } + + //=========== Filtered Deck List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Deck} backed by the internal list. + */ + @Override + public ObservableList getFilteredDeckList() { + return filteredDecks; + } + + /** + * Returns an unmodifiable view of the list of {@code Card} backed by the internal list. + */ + @Override + public ObservableList getFilteredCardList() { + return filteredDecks.get(deckIndex.get().getZeroBased()).asUnmodifiableObservableList(); + } + + /** + * Updates the filter of the filtered deck list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + @Override + public void updateFilteredDeckList(Predicate predicate) { + requireNonNull(predicate); + filteredDecks.setPredicate(predicate); + } + + + /** + * Starts a game session with a given deck index. + * @param index index of the deck to play with. + * @return a randomly selected card from the deck + */ + @Override + public Card play(Index index) { + Deck deck = library.getDeck(index); + if (deck == null) { + return null; + } + if (deck.asUnmodifiableObservableList().size() == 0) { + return new Card(null, null); + } + this.game = new GameManager(deck); + this.view = View.PLAY; + setCurrentView(View.PLAY); + Card card = deck.asUnmodifiableObservableList().get(game.getCurrCardIdx()); + setPlayingCard(card); + setCardAttempted(0); + setCardRemaining(game.getDeckSize()); + setFlipped(false); + return card; + } + + //=========== Play View ====================================================================== + /** + * Flips the card to the back face. + * @return true if the card has not been flipped, false otherwise + */ + @Override + public BackFace flip() { + setFlipped(true); + return this.game.flip(); + } + + /** + * Returns the next card after user answers Yes. + * @return the next card or null if card list is empty + */ + @Override + public Card answerYes() { + Card card = this.game.answerYes(); + if (card == null) { + //Statistics statistics = stop(); + } + setPlayingCard(card); + setFlipped(false); + setCardAttempted(game.getCardAttempted()); + setCardRemaining(game.getDeckSize()); + return card; + } + + /** + * Returns the next card after user answers No. + * @return the next card or null if card list is empty + */ + @Override + public Card answerNo() { + Card card = this.game.answerNo(); + if (card == null) { + //Statistics statistics = stop(); + } + setPlayingCard(card); + setFlipped(false); + setCardAttempted(game.getCardAttempted()); + setCardRemaining(game.getDeckSize()); + return card; + } + + + /** + * Returns the game manager object. + */ + public GameManager getGame() { + return this.game; + } + + + /** + * Stops the game session. + * @return the statistics report. + */ + @Override + public Statistics stop() { + Statistics statistics = this.game.stop(); + this.game = null; + setPlayingCard(null); + returnToLibrary(); + this.view = View.LIBRARY; + setCurrentView(View.LIBRARY); + return statistics; + } + + + @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) + && userPrefs.equals(other.userPrefs) + && filteredDecks.equals(other.filteredDecks); + } + + + //=========== SimpleObjectProperty ====================================================================== + + /** + * Return selected Deck SimpleObjectProperty + */ + @Override + public ReadOnlyProperty selectedDeckProperty() { + return selectedDeck; + } + + /** + * Return the current View SimpleObjectProperty + */ + @Override + public ReadOnlyProperty currentViewProperty() { + return currentView; + } + + /** + * Return the Card that is been playing SimpleObjectProperty + */ + @Override + public ReadOnlyProperty playingCardProperty() { + return playingCard; + } + + /** + * Return the Flipped status SimpleObjectProperty + */ + @Override + public ReadOnlyProperty flippedProperty() { + return flipped; + } + + + /** + * Return the no of Cards attempted SimpleObjectProperty + */ + @Override + public ReadOnlyProperty cardAttemptedProperty() { + return cardAttempted; + } + + /** + * Return the no of Cards remaining SimpleObjectProperty + */ + @Override + public ReadOnlyProperty cardRemainingProperty() { + return cardRemaining; + } + + /** + * * Sets the selected deck Read-only Property + */ + @Override + public void setSelectedDeck(Deck deck) { + if (deck != null) { + selectedDeck.setValue(deck); + Index currIndex = Index.fromZeroBased(library.getDeckList().indexOf(deck)); + deckIndex = Optional.of(currIndex); + this.view = View.DECK; + } + + } + + /** + * Sets the value of currentView in Read-only Property + * Toggle currentView between LIBRARY and PLAY only to switch between CardListPanel and PlayPanel + */ + @Override + public void setCurrentView(View view) { + currentView.setValue(view); + } + + /** + * Sets the playing card Read-only property + */ + @Override + public void setPlayingCard(Card card) { + playingCard.setValue(card); + } + + /** + * Sets the value of flipped Read-only Property + */ + @Override + public void setFlipped(Boolean value) { + flipped.setValue(value); + } + + /** + * Sets the number of cards attempted Read-only Property + */ + @Override + public void setCardAttempted(int value) { + cardAttempted.setValue(value); + } + + /** + * Sets the number of remaining cards Read-only Property + */ + @Override + public void setCardRemaining(int value) { + cardRemaining.setValue(value); + } + +} diff --git a/src/main/java/com/flashspeed/model/ReadOnlyLibrary.java b/src/main/java/com/flashspeed/model/ReadOnlyLibrary.java new file mode 100644 index 00000000000..81e42baa5c6 --- /dev/null +++ b/src/main/java/com/flashspeed/model/ReadOnlyLibrary.java @@ -0,0 +1,17 @@ +package com.flashspeed.model; + +import com.flashspeed.model.deck.Deck; + +import javafx.collections.ObservableList; + +/** + * Unmodifiable view of a library. + */ +public interface ReadOnlyLibrary { + + /** + * Returns an unmodifiable view of the decks list. + * This list will not contain any duplicate decks. + */ + ObservableList getDeckList(); +} diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/com/flashspeed/model/ReadOnlyUserPrefs.java similarity index 57% rename from src/main/java/seedu/address/model/ReadOnlyUserPrefs.java rename to src/main/java/com/flashspeed/model/ReadOnlyUserPrefs.java index befd58a4c73..1ab14fc8089 100644 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ b/src/main/java/com/flashspeed/model/ReadOnlyUserPrefs.java @@ -1,8 +1,8 @@ -package seedu.address.model; +package com.flashspeed.model; import java.nio.file.Path; -import seedu.address.commons.core.GuiSettings; +import com.flashspeed.commons.core.GuiSettings; /** * Unmodifiable view of user prefs. @@ -11,6 +11,5 @@ public interface ReadOnlyUserPrefs { GuiSettings getGuiSettings(); - Path getAddressBookFilePath(); - + Path getLibraryFilePath(); } diff --git a/src/main/java/com/flashspeed/model/Statistics.java b/src/main/java/com/flashspeed/model/Statistics.java new file mode 100644 index 00000000000..04e9dddef7d --- /dev/null +++ b/src/main/java/com/flashspeed/model/Statistics.java @@ -0,0 +1,182 @@ +package com.flashspeed.model; + +import java.util.HashMap; +import java.util.Map; + +import com.flashspeed.model.deck.card.Card; + +import javafx.collections.ObservableList; + +/** + * Represents statistics report for one game session. + */ +public class Statistics { + + private static final String NEW_GAME_ERR_MSG = "The game has not started yet!"; + + // number of correct answers + private int correctAns; + // number of wrong answer + private int wrongAns; + + //Total number of questions played + private int totalQns; + + // hash map to keep track the number of attempts to get the correct answer for each card + private Map totalAttempts; + private Map correctAttempts; + private Map wrongAttempts; + private Map cardTracker; + + Statistics(int correctAns, int wrongAns, int totalQns, Map totalAttempts, + Map correctAttempts, + Map wrongAttempts, ObservableList cards) { + this.correctAns = correctAns; + this.wrongAns = wrongAns; + this.totalQns = totalQns; + this.totalAttempts = totalAttempts; + this.correctAttempts = correctAttempts; + this.wrongAttempts = wrongAttempts; + this.cardTracker = new HashMap<>() { + { + for (Card card: cards) { + put(card, 1); + } + } + }; + } + + public Statistics(ObservableList cards) { + this.correctAns = 0; + this.wrongAns = 0; + this.totalQns = 0; + this.totalAttempts = new HashMap<>(); + this.correctAttempts = new HashMap<>(); + this.wrongAttempts = new HashMap<>(); + this.cardTracker = new HashMap<>(); + + // initialize the number of attempt for each card as 0 + for (int i = 0; i < cards.size(); i++) { + totalAttempts.put(cards.get(i), 0); + correctAttempts.put(cards.get(i), 0); + wrongAttempts.put(cards.get(i), 0); + cardTracker.put(cards.get(i), 1); + } + } + + /** + * Returns the number of correct answers so far. + */ + public int getCorrectAns() { + assert(correctAns >= 0); + return this.correctAns; + } + + /** + * Returns the number of incorrect answers so far. + */ + public int getWrongAns() { + assert(wrongAns >= 0); + return this.wrongAns; + } + + /** + * Returns the total number of cards played so far. + */ + public int getTotalQns() { + assert(totalQns >= 0); + return this.totalQns; + } + + /** + * Calculates the current score of the game. + * @return the current score of the game thus far. + */ + public long getScore() throws ArithmeticException { + if (totalQns == 0) { + throw new ArithmeticException(NEW_GAME_ERR_MSG); + } + return Math.round(Double.valueOf(correctAns) / Double.valueOf(totalQns) * 100); + } + + /** + * Increments the number of attempts of a certain card. + */ + private void incrementAttempt(Card card) { + ++totalQns; + totalAttempts.merge(card, 1, Integer::sum); + assert(totalAttempts.get(card) > 0); + assert(totalQns > 0); + } + + /** + * Increments the number of correct attempts of a certain card. + */ + public void incrementCorrectAttempt(Card card) { + ++correctAns; + correctAttempts.merge(card, 1, Integer::sum); + cardTracker.merge(card, -1, Integer::sum); + incrementAttempt(card); + } + + /** + * Increments the number of correct attempts of a certain card. + * + * @return whether there are 2 cards in the current game + */ + public boolean incrementWrongAttempt(Card card) { + ++wrongAns; + assert(wrongAns > 0); + wrongAttempts.merge(card, 1, Integer::sum); + incrementAttempt(card); + int numCardsInDeck = cardTracker.containsKey(card) ? cardTracker.get(card) : 0; + + if (numCardsInDeck > 2) { + // should never happen, but reset to 2 so no cards can be added. + numCardsInDeck = 2; + cardTracker.replace(card, 2); + } + + if (numCardsInDeck < 2) { + cardTracker.merge(card, 1, Integer::sum); + return false; + } else { + return true; + } + } + + @Override + public String toString() { + int wrongCounter = 0; + String output = "Cards Attempted: " + this.totalQns + "\n" + + "Correct Attempts: " + this.correctAns + "\n" + + "Wrong Attempts: " + this.wrongAns + "\n" + + "Score: " + String.format("%2d", getScore()) + "% \n"; + + for (Map.Entry entry: wrongAttempts.entrySet()) { + if (entry.getValue() > 0) { + wrongCounter++; + } + } + + if (this.totalQns == 0) { + output += "You did not attempt any card!" + "\n"; + } else if (wrongCounter > 0) { + output += "Here is a list of cards you guessed incorrectly:" + "\n"; + for (Map.Entry entry: wrongAttempts.entrySet()) { + if (entry.getValue() > 0) { + output += String.format("%s : %s (%d %s)\n", + entry.getKey().getFrontFace().getValue(), + entry.getKey().getBackFace().getValue(), + entry.getValue(), + entry.getValue() == 1 ? "time" : "times"); + } + } + } else { + output += "Congratulations! You got them all correct!" + "\n"; + } + + return output; + } + +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/com/flashspeed/model/UserPrefs.java similarity index 67% rename from src/main/java/seedu/address/model/UserPrefs.java rename to src/main/java/com/flashspeed/model/UserPrefs.java index 25a5fd6eab9..158d6c86097 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/com/flashspeed/model/UserPrefs.java @@ -1,4 +1,4 @@ -package seedu.address.model; +package com.flashspeed.model; import static java.util.Objects.requireNonNull; @@ -6,7 +6,7 @@ import java.nio.file.Paths; import java.util.Objects; -import seedu.address.commons.core.GuiSettings; +import com.flashspeed.commons.core.GuiSettings; /** * Represents User's preferences. @@ -14,7 +14,7 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path libraryFilePath = Paths.get("data" , "library.json"); /** * Creates a {@code UserPrefs} with default values. @@ -35,7 +35,7 @@ public UserPrefs(ReadOnlyUserPrefs userPrefs) { public void resetData(ReadOnlyUserPrefs newUserPrefs) { requireNonNull(newUserPrefs); setGuiSettings(newUserPrefs.getGuiSettings()); - setAddressBookFilePath(newUserPrefs.getAddressBookFilePath()); + setLibraryFilePath(newUserPrefs.getLibraryFilePath()); } public GuiSettings getGuiSettings() { @@ -47,13 +47,13 @@ public void setGuiSettings(GuiSettings guiSettings) { this.guiSettings = guiSettings; } - public Path getAddressBookFilePath() { - return addressBookFilePath; + public Path getLibraryFilePath() { + return libraryFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - this.addressBookFilePath = addressBookFilePath; + public void setLibraryFilePath(Path libraryFilePath) { + requireNonNull(libraryFilePath); + this.libraryFilePath = libraryFilePath; } @Override @@ -61,27 +61,26 @@ public boolean equals(Object other) { if (other == this) { return true; } - if (!(other instanceof UserPrefs)) { //this handles null as well. + if (!(other instanceof UserPrefs)) { // this handles null as well return false; } UserPrefs o = (UserPrefs) other; return guiSettings.equals(o.guiSettings) - && addressBookFilePath.equals(o.addressBookFilePath); + && libraryFilePath.equals(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); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("\nLocal data file location : " + libraryFilePath); return sb.toString(); } - } diff --git a/src/main/java/com/flashspeed/model/deck/Deck.java b/src/main/java/com/flashspeed/model/deck/Deck.java new file mode 100644 index 00000000000..733294cde26 --- /dev/null +++ b/src/main/java/com/flashspeed/model/deck/Deck.java @@ -0,0 +1,134 @@ +package com.flashspeed.model.deck; + +import static java.util.Objects.requireNonNull; + +import java.util.Objects; + +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.model.deck.card.Card; +import com.flashspeed.model.deck.card.UniqueCardList; + +import javafx.collections.ObservableList; + +/** + * Represents a Deck in the library. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Deck { + // Identity fields + private Name name; + + // Data fields + private UniqueCardList cards = new UniqueCardList(); + + /** + * Every field must be present and not null. + */ + public Deck(Name name) { + requireNonNull(name); + this.name = name; + } + + public Name getName() { + return name; + } + + public int getSize() { + return cards.getSize(); + } + + /** + * Returns true if the list contains an equivalent card as the given argument. + */ + public boolean contains(Card toCheck) { + return cards.contains(toCheck); + } + + public Card getCard(Index index) { + return cards.getCard(index); + } + + /** + * Adds a card to the deck. + * The card must not already exist in the list. + */ + public void add(Card toAdd) { + cards.add(toAdd); + } + + /** + * Removes the equivalent card from the list. + * The card must exist in the list. + */ + public void remove(Card toRemove) { + cards.remove(toRemove); + } + + public void setName(Name newName) { + this.name = newName; + } + + /** + * Replaces the equivalent card from the list with the new card. + * The old card must exist in the list and the new card must not already exist in the list. + */ + public void replace(Card toRemove, Card toAdd) { + cards.replace(toRemove, toAdd); + } + + /** + * Returns true if both decks have the same name. + * This defines a weaker notion of equality between two decks. + */ + public boolean isSameDeck(Deck otherDeck) { + if (otherDeck == this) { + return true; + } + + return otherDeck != null + && otherDeck.getName().equalsLowerCase(getName()); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return cards.asUnmodifiableObservableList(); + } + + public ObservableList asObservableList() { + return cards.asObservableList(); + } + + /** + * Returns true if both decks have the same identity and data fields. + * This defines a stronger notion of equality between two decks. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Deck)) { + return false; + } + + Deck otherDeck = (Deck) other; + return otherDeck.getName().equalsLowerCase(getName()); + } + + @Override + public int hashCode() { + // use method for custom field hashing instead of implementing your own + return Objects.hash(name); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()); + + return builder.toString(); + } +} diff --git a/src/main/java/com/flashspeed/model/deck/Name.java b/src/main/java/com/flashspeed/model/deck/Name.java new file mode 100644 index 00000000000..d9bb6bad751 --- /dev/null +++ b/src/main/java/com/flashspeed/model/deck/Name.java @@ -0,0 +1,65 @@ +package com.flashspeed.model.deck; + +import static java.util.Objects.requireNonNull; + +import com.flashspeed.commons.util.AppUtil; + +/** + * Represents a Deck's name in FlashSpeed. + * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} + */ +public class Name { + + public static final String MESSAGE_CONSTRAINTS = + "Names should contain at least one (non-whitespace) character, i.e. should not be blank"; + + public final String name; + + /** + * Constructs a {@code Name}. + * + * @param name A valid name. + */ + public Name(String name) { + requireNonNull(name); + name = name.strip(); + AppUtil.checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); + this.name = name; + } + + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidName(String test) { + return !test.strip().isEmpty(); + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Name // instanceof handles nulls + && name.equals(((Name) other).name)); // state check + } + + /** + * Checks if the lower case form of the name is equal to another object + * + * @param other the object being compared with. + * @return True if the lowercase form of the name is equal to (@code other). + */ + public boolean equalsLowerCase(Object other) { + return other == this // short circuit if same object + || (other instanceof Name // instanceof handles nulls + && name.toLowerCase().equals(((Name) other).name.toLowerCase())); // state check + } + + @Override + public int hashCode() { + return name.hashCode(); + } +} diff --git a/src/main/java/com/flashspeed/model/deck/UniqueDeckList.java b/src/main/java/com/flashspeed/model/deck/UniqueDeckList.java new file mode 100644 index 00000000000..fee7591b4b5 --- /dev/null +++ b/src/main/java/com/flashspeed/model/deck/UniqueDeckList.java @@ -0,0 +1,153 @@ +package com.flashspeed.model.deck; + +import static java.util.Objects.requireNonNull; + +import java.util.Iterator; +import java.util.List; + +import com.flashspeed.commons.util.CollectionUtil; +import com.flashspeed.model.deck.exceptions.DeckNotFoundException; +import com.flashspeed.model.deck.exceptions.DuplicateDeckException; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * A list of decks that enforces uniqueness between its elements and does not allow nulls. + * A deck is considered unique by comparing using {@code Deck#isSameDeck(Deck)}. As such, adding and updating of + * decks uses Deck#isSameDeck(Deck) for equality so as to ensure that the deck being added or updated is + * unique in terms of identity in the UniqueDeckList. However, the removal of a deck uses Deck#equals(Object) so + * as to ensure that the deck with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Deck#isSameDeck(Deck) + */ +public class UniqueDeckList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent deck as the given argument. + */ + public boolean contains(Deck toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameDeck); + } + + /** + * Returns deck with the given index. + */ + public Deck get(int index) { + if (index < 0 || index >= internalList.size()) { + return null; + } + return internalList.get(index); + } + + /** + * Adds a deck to the library. + * The deck must not already exist in the library. + */ + public void add(Deck toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateDeckException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the deck {@code target} in the list with {@code editedDeck}. + * {@code target} must exist in the list. + * The deck identity of {@code editedDeck} must not be the same as another existing deck in the list. + */ + public void setDeck(Deck target, Deck editedDeck) { + CollectionUtil.requireAllNonNull(target, editedDeck); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new DeckNotFoundException(); + } + + if (!target.isSameDeck(editedDeck) && contains(editedDeck)) { + throw new DuplicateDeckException(); + } + + internalList.set(index, editedDeck); + } + + + /** + * Removes the equivalent deck from the library. + * The deck must exist in the library. + */ + public void remove(Deck toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new DeckNotFoundException(); + } + } + + public void setDecks(UniqueDeckList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of the library with {@code decks}. + * {@code decks} must not contain duplicate decks. + */ + public void setDecks(List decks) { + CollectionUtil.requireAllNonNull(decks); + if (!decksAreUnique(decks)) { + throw new DuplicateDeckException(); + } + + internalList.setAll(decks); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueDeckList // instanceof handles nulls + && internalList.equals(((UniqueDeckList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code decks} contains only unique decks. + */ + private boolean decksAreUnique(List decks) { + for (int i = 0; i < decks.size() - 1; i++) { + for (int j = i + 1; j < decks.size(); j++) { + if (decks.get(i).isSameDeck(decks.get(j))) { + return false; + } + } + } + return true; + } + + public int getSize() { + return internalList.size(); + } +} diff --git a/src/main/java/com/flashspeed/model/deck/card/BackFace.java b/src/main/java/com/flashspeed/model/deck/card/BackFace.java new file mode 100644 index 00000000000..b8f1c998330 --- /dev/null +++ b/src/main/java/com/flashspeed/model/deck/card/BackFace.java @@ -0,0 +1,11 @@ +package com.flashspeed.model.deck.card; + +/** + * Represents the back face of a card. + */ +public class BackFace extends Face { + + public BackFace(String value) { + super(value.strip()); + } +} diff --git a/src/main/java/com/flashspeed/model/deck/card/Card.java b/src/main/java/com/flashspeed/model/deck/card/Card.java new file mode 100644 index 00000000000..633830c3c0e --- /dev/null +++ b/src/main/java/com/flashspeed/model/deck/card/Card.java @@ -0,0 +1,59 @@ +package com.flashspeed.model.deck.card; + +import java.util.Objects; + +/** + * Represents a Card in the deck. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Card { + + private final FrontFace frontFace; + private final BackFace backFace; + + /** + * Every field must be present and not null. + */ + public Card(FrontFace frontFace, BackFace backFace) { + this.frontFace = frontFace; + this.backFace = backFace; + } + + public FrontFace getFrontFace() { + return this.frontFace; + } + + public BackFace getBackFace() { + return this.backFace; + } + + /** + * Returns true if both cards have the same front and back faces. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Card)) { + return false; + } + + Card otherCard = (Card) other; + return otherCard.getFrontFace().equals(getFrontFace()) + && otherCard.getBackFace().equals(getBackFace()); + } + + @Override + public int hashCode() { + return Objects.hash(frontFace, backFace); + }; + + @Override + public String toString() { + return getFrontFace() + + " : " + + getBackFace(); + } +} diff --git a/src/main/java/com/flashspeed/model/deck/card/Face.java b/src/main/java/com/flashspeed/model/deck/card/Face.java new file mode 100644 index 00000000000..62e384560ed --- /dev/null +++ b/src/main/java/com/flashspeed/model/deck/card/Face.java @@ -0,0 +1,43 @@ +package com.flashspeed.model.deck.card; + +/** + * Represents a face of a card. + */ +public abstract class Face { + private final String value; + + public Face(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return value; + } + + /** + * Returns true if both faces have the same value. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Face)) { + return false; + } + + Face otherFace = (Face) other; + return otherFace.getValue().equals(getValue()); + } +} diff --git a/src/main/java/com/flashspeed/model/deck/card/FrontFace.java b/src/main/java/com/flashspeed/model/deck/card/FrontFace.java new file mode 100644 index 00000000000..7567bbff6b5 --- /dev/null +++ b/src/main/java/com/flashspeed/model/deck/card/FrontFace.java @@ -0,0 +1,11 @@ +package com.flashspeed.model.deck.card; + +/** + * Represents the front face of a card. + */ +public class FrontFace extends Face { + + public FrontFace(String value) { + super(value.strip()); + } +} diff --git a/src/main/java/com/flashspeed/model/deck/card/UniqueCardList.java b/src/main/java/com/flashspeed/model/deck/card/UniqueCardList.java new file mode 100644 index 00000000000..d0ec806ddf8 --- /dev/null +++ b/src/main/java/com/flashspeed/model/deck/card/UniqueCardList.java @@ -0,0 +1,135 @@ +package com.flashspeed.model.deck.card; + +import static com.flashspeed.commons.util.CollectionUtil.requireAllNonNull; +import static java.util.Objects.requireNonNull; + +import java.util.Iterator; +import java.util.List; + +import com.flashspeed.commons.core.index.Index; +import com.flashspeed.model.deck.exceptions.CardNotFoundException; +import com.flashspeed.model.deck.exceptions.DuplicateCardException; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * A list of cards that enforces uniqueness between its elements and does not allow nulls. + * A card is considered unique by comparing using {@code Card#equals(Object)}. As such, adding and updating of + * cards uses Card#equals(Object) for equality so as to ensure that the card being added or updated is + * unique in terms of content in the UniqueCardList. The removal of a card also uses Card#equals(Object) + * + * Supports a minimal set of list operations. + * + * @see Card#equals(Object) + */ +public class UniqueCardList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent card as the given argument. + */ + public boolean contains(Card toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::equals); + } + + public int getSize() { + return internalList.size(); + } + + /** + * Adds a card to the list. + * The card must not already exist in the list. + */ + public void add(Card toAdd) throws DuplicateCardException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateCardException(); + } + internalList.add(toAdd); + } + + public Card getCard(Index index) { + return internalList.get(index.getZeroBased()); + } + + /** + * Removes the equivalent card from the list. + * The card must exist in the list. + */ + public void remove(Card toRemove) throws DuplicateCardException { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new CardNotFoundException(); + } + } + + /** + * Replaces the card {@code toRemove} in the list with {@code toAdd}. {@code toRemove} must exist in the list. + * The card identity of {@code toAdd} must not be the same as another existing card in the list. + */ + public void replace(Card toRemove, Card toAdd) throws CardNotFoundException, DuplicateCardException { + requireAllNonNull(toRemove, toAdd); + + int idx = internalList.indexOf(toRemove); + if (idx == -1) { + throw new CardNotFoundException(); + } + if (!toRemove.equals(toAdd) && contains(toAdd)) { + throw new DuplicateCardException(); + } + + internalList.set(idx, toAdd); + } + + public void setCards(UniqueCardList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + public ObservableList asObservableList() { + return FXCollections.observableArrayList(internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueCardList // instanceof handles nulls + && internalList.equals(((UniqueCardList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code cards} contains only unique cards. + */ + private boolean cardsAreUnique(List cards) { + for (int i = 0; i < cards.size() - 1; i++) { + for (int j = i + 1; j < cards.size(); j++) { + if (cards.get(i).equals(cards.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/com/flashspeed/model/deck/exceptions/CardNotFoundException.java b/src/main/java/com/flashspeed/model/deck/exceptions/CardNotFoundException.java new file mode 100644 index 00000000000..8e51c563a9a --- /dev/null +++ b/src/main/java/com/flashspeed/model/deck/exceptions/CardNotFoundException.java @@ -0,0 +1,6 @@ +package com.flashspeed.model.deck.exceptions; + +/** + * Signals that the operation is unable to find the specified card. + */ +public class CardNotFoundException extends RuntimeException {} diff --git a/src/main/java/com/flashspeed/model/deck/exceptions/DeckNotFoundException.java b/src/main/java/com/flashspeed/model/deck/exceptions/DeckNotFoundException.java new file mode 100644 index 00000000000..287bd7dfe97 --- /dev/null +++ b/src/main/java/com/flashspeed/model/deck/exceptions/DeckNotFoundException.java @@ -0,0 +1,6 @@ +package com.flashspeed.model.deck.exceptions; + +/** + * Signals that the operation is unable to find the specified deck. + */ +public class DeckNotFoundException extends RuntimeException {} diff --git a/src/main/java/com/flashspeed/model/deck/exceptions/DuplicateCardException.java b/src/main/java/com/flashspeed/model/deck/exceptions/DuplicateCardException.java new file mode 100644 index 00000000000..ecc43f5cfca --- /dev/null +++ b/src/main/java/com/flashspeed/model/deck/exceptions/DuplicateCardException.java @@ -0,0 +1,11 @@ +package com.flashspeed.model.deck.exceptions; + +/** + * Signals that the operation will result in duplicate Cards (Cards are considered duplicates if they have the same + * identity). + */ +public class DuplicateCardException extends RuntimeException { + public DuplicateCardException() { + super("Operation would result in duplicate cards"); + } +} diff --git a/src/main/java/com/flashspeed/model/deck/exceptions/DuplicateDeckException.java b/src/main/java/com/flashspeed/model/deck/exceptions/DuplicateDeckException.java new file mode 100644 index 00000000000..73dec15b9da --- /dev/null +++ b/src/main/java/com/flashspeed/model/deck/exceptions/DuplicateDeckException.java @@ -0,0 +1,11 @@ +package com.flashspeed.model.deck.exceptions; + +/** + * Signals that the operation will result in duplicate Decks (Decks are considered duplicates if they have the same + * identity). + */ +public class DuplicateDeckException extends RuntimeException { + public DuplicateDeckException() { + super("Operation would result in duplicate decks"); + } +} diff --git a/src/main/java/com/flashspeed/model/util/SampleDataUtil.java b/src/main/java/com/flashspeed/model/util/SampleDataUtil.java new file mode 100644 index 00000000000..b5710933fc3 --- /dev/null +++ b/src/main/java/com/flashspeed/model/util/SampleDataUtil.java @@ -0,0 +1,30 @@ +package com.flashspeed.model.util; + +import com.flashspeed.model.Library; +import com.flashspeed.model.ReadOnlyLibrary; +import com.flashspeed.model.deck.Deck; +import com.flashspeed.model.deck.Name; +import com.flashspeed.model.deck.card.BackFace; +import com.flashspeed.model.deck.card.Card; +import com.flashspeed.model.deck.card.FrontFace; + +/** + * Contains utility methods for populating {@code Library} with sample data. + */ +public class SampleDataUtil { + public static Deck[] getSampleDecks() { + Deck sampleDeck = new Deck(new Name("Sample Deck 1")); + sampleDeck.add(new Card(new FrontFace("front"), new BackFace("back"))); + return new Deck[] { + sampleDeck + }; + } + + public static ReadOnlyLibrary getSampleLibrary() { + Library sampleLib = new Library(); + for (Deck sampleDeck : getSampleDecks()) { + sampleLib.addDeck(sampleDeck); + } + return sampleLib; + } +} diff --git a/src/main/java/com/flashspeed/model/util/View.java b/src/main/java/com/flashspeed/model/util/View.java new file mode 100644 index 00000000000..727c4efb2d2 --- /dev/null +++ b/src/main/java/com/flashspeed/model/util/View.java @@ -0,0 +1,10 @@ +package com.flashspeed.model.util; + +/** + * Defines the different views a user can be in at any point in time. + */ +public enum View { + LIBRARY, + DECK, + PLAY +} diff --git a/src/main/java/com/flashspeed/storage/JsonAdaptedCard.java b/src/main/java/com/flashspeed/storage/JsonAdaptedCard.java new file mode 100644 index 00000000000..97ba528bc07 --- /dev/null +++ b/src/main/java/com/flashspeed/storage/JsonAdaptedCard.java @@ -0,0 +1,67 @@ +package com.flashspeed.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import com.flashspeed.commons.exceptions.IllegalValueException; +import com.flashspeed.model.deck.card.BackFace; +import com.flashspeed.model.deck.card.Card; +import com.flashspeed.model.deck.card.FrontFace; + +/** + * Jackson-friendly version of {@link Card}. + */ +class JsonAdaptedCard { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Card name is missing!"; + + private final String frontFace; + private final String backFace; + + /** + * Constructs a {@code JsonAdaptedCard} with the given deck details. + */ + @JsonCreator + public JsonAdaptedCard(@JsonProperty("frontFace") String frontFace, @JsonProperty("backFace") String backFace) { + this.frontFace = frontFace; + this.backFace = backFace; + } + + /** + * Converts a given {@code Card} into this class for Jackson use. + */ + public JsonAdaptedCard(Card card) { + this.frontFace = card.getFrontFace().getValue(); + this.backFace = card.getBackFace().getValue(); + } + + /** + * Getters for front face value. + */ + public String getFrontFace() { + return frontFace; + } + + /** + * Getters for Back face value. + */ + public String getBackFace() { + return backFace; + } + + /** + * Converts this Jackson-friendly adapted deck object into the model's {@code Card} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted deck. + */ + public Card toModelType() throws IllegalValueException { + if (frontFace == null || backFace == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT)); + } + + final FrontFace front = new FrontFace(frontFace); + final BackFace back = new BackFace(backFace); + + return new Card(front, back); + } +} diff --git a/src/main/java/com/flashspeed/storage/JsonAdaptedDeck.java b/src/main/java/com/flashspeed/storage/JsonAdaptedDeck.java new file mode 100644 index 00000000000..56279da74fb --- /dev/null +++ b/src/main/java/com/flashspeed/storage/JsonAdaptedDeck.java @@ -0,0 +1,87 @@ +package com.flashspeed.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import com.flashspeed.commons.exceptions.IllegalValueException; +import com.flashspeed.model.deck.Deck; +import com.flashspeed.model.deck.Name; +import com.flashspeed.model.deck.card.BackFace; +import com.flashspeed.model.deck.card.Card; +import com.flashspeed.model.deck.card.FrontFace; + +/** + * Jackson-friendly version of {@link Deck}. + */ +class JsonAdaptedDeck { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = + "Names should contain at least one (non-whitespace) character, i.e. should not be blank"; + private String name; + private List cards = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedDeck} with the given deck details. + */ + @JsonCreator + public JsonAdaptedDeck(@JsonProperty("name") String name, @JsonProperty("cards") List cards) { + this.name = name; + this.cards.addAll(cards); + } + + /** + * Converts a given {@code Deck} into this class for Jackson use. + */ + public JsonAdaptedDeck(Deck source) { + name = source.getName().name; + this.cards = source.asUnmodifiableObservableList() + .stream() + .map(card -> new JsonAdaptedCard(card)) + .collect(Collectors.toList()); + } + + public JsonAdaptedDeck(Name name, List cards) throws IllegalValueException { + this.name = name.name; + if (cards == null) { + throw new IllegalValueException(MISSING_FIELD_MESSAGE_FORMAT); + } + this.cards = cards.stream().map(card -> new JsonAdaptedCard(card)) + .collect(Collectors.toList()); + + } + + /** + * Changes the name of the JSON deck. + * + * @param name The new name of the deck. + * @return The deck with the new name. + */ + public JsonAdaptedDeck setName(String name) { + this.name = name; + return this; + } + + /** + * Converts this Jackson-friendly adapted deck object into the model's {@code Deck} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted deck. + */ + public Deck toModelType() throws IllegalValueException { + if (name == null) { + throw new IllegalValueException(MISSING_FIELD_MESSAGE_FORMAT); + } + + Deck modelDeck = new Deck(new Name(name)); + for (JsonAdaptedCard card : cards) { + FrontFace newFrontFace = new FrontFace(card.getFrontFace()); + BackFace newBackFace = new BackFace(card.getBackFace()); + modelDeck.add(new Card(newFrontFace, newBackFace)); + } + + return modelDeck; + } +} diff --git a/src/main/java/com/flashspeed/storage/JsonLibraryStorage.java b/src/main/java/com/flashspeed/storage/JsonLibraryStorage.java new file mode 100644 index 00000000000..f541f1f4a9f --- /dev/null +++ b/src/main/java/com/flashspeed/storage/JsonLibraryStorage.java @@ -0,0 +1,79 @@ +package com.flashspeed.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import com.flashspeed.commons.core.LogsCenter; +import com.flashspeed.commons.exceptions.DataConversionException; +import com.flashspeed.commons.exceptions.IllegalValueException; +import com.flashspeed.commons.util.FileUtil; +import com.flashspeed.commons.util.JsonUtil; +import com.flashspeed.model.ReadOnlyLibrary; + +/** + * A class to access Library data stored as a 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 Optional readLibrary() throws DataConversionException { + 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 Optional readLibrary(Path filePath) throws DataConversionException { + requireNonNull(filePath); + + Optional jsonLibrary = JsonUtil.readJsonFile( + filePath, JsonSerializableLibrary.class); + if (!jsonLibrary.isPresent()) { + return Optional.empty(); + } + + try { + return Optional.of(jsonLibrary.get().toModelType()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataConversionException(ive); + } + } + + @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); + JsonUtil.saveJsonFile(new JsonSerializableLibrary(library), filePath); + } +} diff --git a/src/main/java/com/flashspeed/storage/JsonSerializableLibrary.java b/src/main/java/com/flashspeed/storage/JsonSerializableLibrary.java new file mode 100644 index 00000000000..d9c5f6f254c --- /dev/null +++ b/src/main/java/com/flashspeed/storage/JsonSerializableLibrary.java @@ -0,0 +1,59 @@ +package com.flashspeed.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import com.flashspeed.commons.exceptions.IllegalValueException; +import com.flashspeed.model.Library; +import com.flashspeed.model.ReadOnlyLibrary; +import com.flashspeed.model.deck.Deck; + +/** + * An Immutable Library that is serializable to JSON format. + */ +@JsonRootName(value = "decks") +class JsonSerializableLibrary { + + public static final String MESSAGE_DUPLICATE_DECK = "Decks list contains duplicate deck(s)."; + + private final List decks = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializableLibrary} with the given decks. + */ + @JsonCreator + public JsonSerializableLibrary(@JsonProperty("decks") List decks) { + this.decks.addAll(decks); + } + + /** + * Converts a given {@code ReadOnlyLibrary} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializableLibrary}. + */ + public JsonSerializableLibrary(ReadOnlyLibrary source) { + decks.addAll(source.getDeckList().stream().map(JsonAdaptedDeck::new).collect(Collectors.toList())); + } + + /** + * Converts this library into the model's {@code Library} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public Library toModelType() throws IllegalValueException { + Library library = new Library(); + for (JsonAdaptedDeck jsonAdaptedDeck : decks) { + Deck deck = jsonAdaptedDeck.toModelType(); + if (library.hasDeck(deck)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_DECK); + } + library.addDeck(deck); + } + return library; + } +} diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/com/flashspeed/storage/JsonUserPrefsStorage.java similarity index 82% rename from src/main/java/seedu/address/storage/JsonUserPrefsStorage.java rename to src/main/java/com/flashspeed/storage/JsonUserPrefsStorage.java index bc2bbad84aa..66e0f817cde 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java +++ b/src/main/java/com/flashspeed/storage/JsonUserPrefsStorage.java @@ -1,16 +1,16 @@ -package seedu.address.storage; +package com.flashspeed.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import com.flashspeed.commons.exceptions.DataConversionException; +import com.flashspeed.commons.util.JsonUtil; +import com.flashspeed.model.ReadOnlyUserPrefs; +import com.flashspeed.model.UserPrefs; /** - * A class to access UserPrefs stored in the hard disk as a json file + * A class to access UserPrefs stored in the hard disk as a json file. */ public class JsonUserPrefsStorage implements UserPrefsStorage { @@ -43,5 +43,4 @@ public Optional readUserPrefs(Path prefsFilePath) throws DataConversi public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { JsonUtil.saveJsonFile(userPrefs, filePath); } - } diff --git a/src/main/java/com/flashspeed/storage/LibraryStorage.java b/src/main/java/com/flashspeed/storage/LibraryStorage.java new file mode 100644 index 00000000000..a51d0461c8b --- /dev/null +++ b/src/main/java/com/flashspeed/storage/LibraryStorage.java @@ -0,0 +1,45 @@ +package com.flashspeed.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import com.flashspeed.commons.exceptions.DataConversionException; +import com.flashspeed.model.Library; +import com.flashspeed.model.ReadOnlyLibrary; + +/** + * Represents a storage for {@link 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. + */ + Optional readLibrary() throws DataConversionException, IOException; + + /** + * @see #getLibraryFilePath() + */ + Optional 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/com/flashspeed/storage/Storage.java b/src/main/java/com/flashspeed/storage/Storage.java new file mode 100644 index 00000000000..1c9ee9422b1 --- /dev/null +++ b/src/main/java/com/flashspeed/storage/Storage.java @@ -0,0 +1,31 @@ +package com.flashspeed.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import com.flashspeed.commons.exceptions.DataConversionException; +import com.flashspeed.model.ReadOnlyLibrary; +import com.flashspeed.model.ReadOnlyUserPrefs; +import com.flashspeed.model.UserPrefs; + +/** + * API of the Storage component + */ +public interface Storage extends LibraryStorage, UserPrefsStorage { + + @Override + Optional readUserPrefs() throws DataConversionException, IOException; + + @Override + void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; + + @Override + Path getLibraryFilePath(); + + @Override + Optional readLibrary() throws DataConversionException, IOException; + + @Override + void saveLibrary(ReadOnlyLibrary library) throws IOException; +} diff --git a/src/main/java/com/flashspeed/storage/StorageManager.java b/src/main/java/com/flashspeed/storage/StorageManager.java new file mode 100644 index 00000000000..69fd49b17ca --- /dev/null +++ b/src/main/java/com/flashspeed/storage/StorageManager.java @@ -0,0 +1,74 @@ +package com.flashspeed.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import com.flashspeed.commons.core.LogsCenter; +import com.flashspeed.commons.exceptions.DataConversionException; +import com.flashspeed.model.ReadOnlyLibrary; +import com.flashspeed.model.ReadOnlyUserPrefs; +import com.flashspeed.model.UserPrefs; + +/** + * Manages storage of library data in local storage. + */ +public class StorageManager 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(ReadOnlyUserPrefs userPrefs) throws IOException { + userPrefsStorage.saveUserPrefs(userPrefs); + } + + + // ================ Library methods ============================== + + @Override + public Path getLibraryFilePath() { + return libraryStorage.getLibraryFilePath(); + } + + @Override + public Optional readLibrary() throws DataConversionException, IOException { + return readLibrary(libraryStorage.getLibraryFilePath()); + } + + @Override + public Optional 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); + } +} diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/com/flashspeed/storage/UserPrefsStorage.java similarity index 71% rename from src/main/java/seedu/address/storage/UserPrefsStorage.java rename to src/main/java/com/flashspeed/storage/UserPrefsStorage.java index 29eef178dbc..8c1356e07d1 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/com/flashspeed/storage/UserPrefsStorage.java @@ -1,15 +1,15 @@ -package seedu.address.storage; +package com.flashspeed.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import com.flashspeed.commons.exceptions.DataConversionException; +import com.flashspeed.model.ReadOnlyUserPrefs; +import com.flashspeed.model.UserPrefs; /** - * Represents a storage for {@link seedu.address.model.UserPrefs}. + * Represents a storage for {@link UserPrefs}. */ public interface UserPrefsStorage { @@ -27,7 +27,7 @@ public interface UserPrefsStorage { Optional readUserPrefs() throws DataConversionException, IOException; /** - * Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage. + * Saves the given {@link ReadOnlyUserPrefs} to the storage. * @param userPrefs cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/com/flashspeed/ui/CardListPanel.java b/src/main/java/com/flashspeed/ui/CardListPanel.java new file mode 100644 index 00000000000..5f64cb355bc --- /dev/null +++ b/src/main/java/com/flashspeed/ui/CardListPanel.java @@ -0,0 +1,93 @@ +package com.flashspeed.ui; + +import java.util.logging.Logger; + +import com.flashspeed.commons.core.LogsCenter; +import com.flashspeed.model.deck.Deck; +import com.flashspeed.model.deck.card.BackFace; +import com.flashspeed.model.deck.card.Card; +import com.flashspeed.model.deck.card.FrontFace; + +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.layout.Region; + +/** + * The Card List Panel of the App. + */ +public class CardListPanel extends UiPart { + + private static final String FXML = "CardListPanel.fxml"; + + private final Logger logger = LogsCenter.getLogger(getClass()); + + @FXML + private Label defaultText; + + @FXML + private TableView itemTbl; + + @SuppressWarnings("unchecked") + public CardListPanel(ObservableValue selectedDeck) { + super(FXML); + + TableColumn indexColumn = new TableColumn<>("ID"); + + indexColumn.setCellValueFactory(column-> new ReadOnlyObjectWrapper( + itemTbl.getItems().indexOf(column.getValue()) + 1)); + + TableColumn frontColumn = new TableColumn<>("Front"); + frontColumn.setCellValueFactory(new PropertyValueFactory<>("frontFace")); + + TableColumn backColumn = new TableColumn<>("Back"); + backColumn.setCellValueFactory(new PropertyValueFactory<>("backFace")); + + // unchecked generics array creation for varargs parameter here + itemTbl.getColumns().addAll(indexColumn, frontColumn, backColumn); + + indexColumn.prefWidthProperty().bind(itemTbl.widthProperty().multiply(0.2)); + frontColumn.prefWidthProperty().bind(itemTbl.widthProperty().multiply(0.4)); + backColumn.prefWidthProperty().bind(itemTbl.widthProperty().multiply(0.4)); + + indexColumn.setSortable(false); + frontColumn.setSortable(false); + backColumn.setSortable(false); + + indexColumn.setResizable(false); + frontColumn.setResizable(false); + backColumn.setResizable(false); + + // Load deck page when selected deck changes. + selectedDeck.addListener((observable, oldValue, newValue) -> { + if (newValue == null) { + itemTbl.getItems().clear(); + defaultText.setText("No deck selected"); + } else { + showCardList(newValue); + } + }); + } + + /** + * Shows Cards of current Deck on right panel. + * + * @param deck current deck + */ + private void showCardList(Deck deck) { + itemTbl.getItems().clear(); + ObservableList cardList = deck.asUnmodifiableObservableList(); + if (cardList.size() == 0) { + defaultText.setText("Selected deck is empty"); + } + for (Card card : cardList) { + itemTbl.getItems().add(card); + } + } + +} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/com/flashspeed/ui/CommandBox.java similarity index 84% rename from src/main/java/seedu/address/ui/CommandBox.java rename to src/main/java/com/flashspeed/ui/CommandBox.java index 7d76e691f52..ec2619b00b5 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/com/flashspeed/ui/CommandBox.java @@ -1,12 +1,15 @@ -package seedu.address.ui; +package com.flashspeed.ui; +import com.flashspeed.logic.Logic; +import com.flashspeed.logic.commands.CommandResult; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.logic.parser.exceptions.ParseException; + +import javafx.application.Platform; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TextField; import javafx.scene.layout.Region; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; /** * The UI component that is responsible for receiving user command inputs. @@ -23,6 +26,7 @@ public class CommandBox extends UiPart { public CommandBox(CommandExecutor commandExecutor) { super(FXML); + Platform.runLater(()->commandTextField.requestFocus()); this.commandExecutor = commandExecutor; // calls #setStyleToDefault() whenever there is a change to the text of the command box. commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); @@ -69,9 +73,8 @@ public interface CommandExecutor { /** * Executes the command and returns the result. * - * @see seedu.address.logic.Logic#execute(String) + * @see Logic#execute(String) */ CommandResult execute(String commandText) throws CommandException, ParseException; } - } diff --git a/src/main/java/com/flashspeed/ui/DeckCard.java b/src/main/java/com/flashspeed/ui/DeckCard.java new file mode 100644 index 00000000000..f2f99c284d7 --- /dev/null +++ b/src/main/java/com/flashspeed/ui/DeckCard.java @@ -0,0 +1,60 @@ +package com.flashspeed.ui; + +import com.flashspeed.model.deck.Deck; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; + +/** + * An UI component that displays information of a {@code Deck}. + */ +public class DeckCard extends UiPart { + + private static final String FXML = "DeckListCard.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. + */ + + public final Deck deck; + + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + + @FXML + private Label cardNo; + + public DeckCard(Deck deck, int displayedIndex) { + super(FXML); + this.deck = deck; + id.setText(displayedIndex + ". "); + name.setText(deck.getName().name); + cardNo.setText(String.valueOf(deck.asUnmodifiableObservableList().size())); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeckCard)) { + return false; + } + + // state check + DeckCard card = (DeckCard) other; + return id.getText().equals(card.id.getText()) + && deck.equals(card.deck); + } +} diff --git a/src/main/java/com/flashspeed/ui/DeckListPanel.java b/src/main/java/com/flashspeed/ui/DeckListPanel.java new file mode 100644 index 00000000000..541e98c8ecb --- /dev/null +++ b/src/main/java/com/flashspeed/ui/DeckListPanel.java @@ -0,0 +1,73 @@ +package com.flashspeed.ui; + +import java.util.function.Consumer; +import java.util.logging.Logger; + +import com.flashspeed.commons.core.LogsCenter; +import com.flashspeed.model.deck.Deck; + +import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; + +/** + * Panel containing the list of decks. + */ +public class DeckListPanel extends UiPart { + private static final String FXML = "DeckListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(DeckListPanel.class); + + @FXML + private ListView deckListView; + + public DeckListPanel(ObservableList deckList, ObservableValue selectedDeck, + Consumer onSelectedDeckChange) { + super(FXML); + deckListView.setItems(deckList); + deckListView.setCellFactory(listView -> new DeckListViewCell()); + // deckListView.getSelectionModel().clearAndSelect(2); + + deckListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + logger.fine("Selection in deck list panel changed to : '" + newValue + "'"); + onSelectedDeckChange.accept(newValue); + }); + + selectedDeck.addListener((observable, oldValue, newValue) -> { + logger.fine("Selected deck changed to: " + newValue); + + // Don't modify selection if we are already selecting the selected deck, + // otherwise we would have an infinite loop. + // if (Objects.equals(deckListView.getSelectionModel().getSelectedItem(), newValue)) { + // return; + // } + + if (newValue == null) { + deckListView.getSelectionModel().clearSelection(); + } else { + int index = deckListView.getItems().indexOf(newValue); + deckListView.scrollTo(index); + deckListView.getSelectionModel().clearAndSelect(index); + } + }); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Deck} using a {@code DeckCard}. + */ + class DeckListViewCell extends ListCell { + @Override + protected void updateItem(Deck deck, boolean empty) { + super.updateItem(deck, empty); + + if (empty || deck == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new DeckCard(deck, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/com/flashspeed/ui/HelpWindow.java similarity index 65% rename from src/main/java/seedu/address/ui/HelpWindow.java rename to src/main/java/com/flashspeed/ui/HelpWindow.java index 9a665915949..278041edace 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/com/flashspeed/ui/HelpWindow.java @@ -1,31 +1,25 @@ -package seedu.address.ui; +package com.flashspeed.ui; import java.util.logging.Logger; +import com.flashspeed.commons.core.LogsCenter; + import javafx.fxml.FXML; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; +import javafx.scene.web.WebView; import javafx.stage.Stage; -import seedu.address.commons.core.LogsCenter; /** * Controller for a help page */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; - public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; + public static final String USERGUIDE_FILE_PATH = "/docs/HelpWindow.html"; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); private static final String FXML = "HelpWindow.fxml"; @FXML - private Button copyButton; - - @FXML - private Label helpMessage; + private WebView browser; /** * Creates a new HelpWindow. @@ -34,7 +28,10 @@ public class HelpWindow extends UiPart { */ public HelpWindow(Stage root) { super(FXML, root); - helpMessage.setText(HELP_MESSAGE); + + String userGuideUrl = getClass().getResource(USERGUIDE_FILE_PATH).toString(); + //String userGuideUrl = "https://ay1920s2-cs2103t-w17-1.github.io/main/UserGuide.html"; + browser.getEngine().load(userGuideUrl); } /** @@ -46,6 +43,7 @@ public HelpWindow() { /** * Shows the help window. + * * @throws IllegalStateException *
    *
  • @@ -65,7 +63,7 @@ public HelpWindow() { public void show() { logger.fine("Showing help page about the application."); getRoot().show(); - getRoot().centerOnScreen(); + // getRoot().centerOnScreen(); } /** @@ -88,15 +86,4 @@ public void hide() { public void focus() { getRoot().requestFocus(); } - - /** - * Copies the URL to the user guide to the clipboard. - */ - @FXML - private void copyUrl() { - final Clipboard clipboard = Clipboard.getSystemClipboard(); - final ClipboardContent url = new ClipboardContent(); - url.putString(USERGUIDE_URL); - clipboard.setContent(url); - } } diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/com/flashspeed/ui/MainWindow.java similarity index 63% rename from src/main/java/seedu/address/ui/MainWindow.java rename to src/main/java/com/flashspeed/ui/MainWindow.java index 90bbf11de97..4b718b53f16 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/com/flashspeed/ui/MainWindow.java @@ -1,21 +1,26 @@ -package seedu.address.ui; +package com.flashspeed.ui; import java.util.logging.Logger; +import com.flashspeed.commons.core.GuiSettings; +import com.flashspeed.commons.core.LogsCenter; +import com.flashspeed.logic.Logic; +import com.flashspeed.logic.commands.CommandResult; +import com.flashspeed.logic.commands.exceptions.CommandException; +import com.flashspeed.logic.parser.exceptions.ParseException; +import com.flashspeed.model.Statistics; +import com.flashspeed.model.util.View; + import javafx.event.ActionEvent; import javafx.fxml.FXML; +import javafx.geometry.Rectangle2D; import javafx.scene.control.MenuItem; import javafx.scene.control.TextInputControl; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; +import javafx.stage.Screen; import javafx.stage.Stage; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.Logic; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; /** * The Main Window. Provides the basic application layout containing @@ -31,9 +36,15 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; + private CardListPanel cardListPanel; + private PlayPanel playPanel; + private DeckListPanel deckListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; + private StatisticsPopUp statisticsPopUp; + + @FXML + private StackPane rightPlaceholder; @FXML private StackPane commandBoxPlaceholder; @@ -42,7 +53,7 @@ public class MainWindow extends UiPart { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane deckListPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -53,6 +64,17 @@ public class MainWindow extends UiPart { public MainWindow(Stage primaryStage, Logic logic) { super(FXML, primaryStage); + //Set window size to be optimal based on screen size + Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds(); + primaryStage.setX(primaryScreenBounds.getMinX()); + primaryStage.setY(primaryScreenBounds.getMinY()); + primaryStage.setWidth(primaryScreenBounds.getWidth()); + primaryStage.setHeight(primaryScreenBounds.getHeight()); + primaryStage.setMinWidth(primaryScreenBounds.getWidth()); + primaryStage.setMinHeight(primaryScreenBounds.getHeight()); + primaryStage.setMaxWidth(primaryScreenBounds.getWidth()); + primaryStage.setMaxHeight(primaryScreenBounds.getHeight()); + // Set dependencies this.primaryStage = primaryStage; this.logic = logic; @@ -63,6 +85,8 @@ public MainWindow(Stage primaryStage, Logic logic) { setAccelerators(); helpWindow = new HelpWindow(); + statisticsPopUp = new StatisticsPopUp(null); + } public Stage getPrimaryStage() { @@ -107,13 +131,25 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + //toggle between Play view and other views depending on the current View. + if (logic.getView() != View.PLAY) { + cardListPanel = new CardListPanel(logic.selectedDeckProperty()); + rightPlaceholder.getChildren().add(cardListPanel.getRoot()); + } else if (logic.getView() == View.PLAY) { + playPanel = new PlayPanel( + logic.playingCardProperty(), logic.flippedProperty(), + logic.cardAttemptedProperty(), logic.cardRemainingProperty()); + rightPlaceholder.getChildren().add(playPanel.getRoot()); + } + + deckListPanel = new DeckListPanel( + logic.getFilteredDeckList(), logic.selectedDeckProperty(), logic::setSelectedDeck); + deckListPanelPlaceholder.getChildren().add(deckListPanel.getRoot()); resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getLibraryFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(this::executeCommand); @@ -144,6 +180,28 @@ public void handleHelp() { } } + /** + * Stops the current play session and shows the statistics window. + * + * @param statistics statistics info of session to be shown + */ + @FXML + public void handleStop(Statistics statistics) { + if (statistics == null) { + return; + } + if (!statisticsPopUp.isShowing()) { + statisticsPopUp = new StatisticsPopUp(statistics); + statisticsPopUp.show(); + } else { + statisticsPopUp.hide(); + statisticsPopUp = new StatisticsPopUp(statistics); + statisticsPopUp.show(); + } + + + } + void show() { primaryStage.show(); } @@ -157,17 +215,18 @@ private void handleExit() { (int) primaryStage.getX(), (int) primaryStage.getY()); logic.setGuiSettings(guiSettings); helpWindow.hide(); + statisticsPopUp.hide(); primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + public DeckListPanel getDeckListPanel() { + return deckListPanel; } /** * Executes the command and returns the result. * - * @see seedu.address.logic.Logic#execute(String) + * @see Logic#execute(String) */ private CommandResult executeCommand(String commandText) throws CommandException, ParseException { try { @@ -183,6 +242,10 @@ private CommandResult executeCommand(String commandText) throws CommandException handleExit(); } + if (commandResult.isStop()) { + handleStop(commandResult.getStatistics()); + } + return commandResult; } catch (CommandException | ParseException e) { logger.info("Invalid command: " + commandText); diff --git a/src/main/java/com/flashspeed/ui/PlayPanel.java b/src/main/java/com/flashspeed/ui/PlayPanel.java new file mode 100644 index 00000000000..1deb7da4b20 --- /dev/null +++ b/src/main/java/com/flashspeed/ui/PlayPanel.java @@ -0,0 +1,121 @@ +package com.flashspeed.ui; + +import java.util.logging.Logger; + +import com.flashspeed.commons.core.LogsCenter; +import com.flashspeed.model.deck.card.Card; + +import javafx.beans.value.ObservableValue; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; + +/** + * The Browser Panel of the App. + */ +public class PlayPanel extends UiPart { + + private static final String FXML = "PlayPanel.fxml"; + + private final Logger logger = LogsCenter.getLogger(getClass()); + + private int attempted; + + private int remaining; + + @FXML + private Label front; + + @FXML + private Label back; + + @FXML + private Label noAttempted; + + @FXML + private Label noRemaining; + + @FXML + private ProgressBar progressBar; + + @FXML + private Label progressPercent; + + @FXML + private VBox progressRoot; + + @FXML + private Label instruction; + + public PlayPanel(ObservableValue playingCard, ObservableValue flipped, + ObservableValue cardAttempted, ObservableValue cardRemaining) { + + //initialize view + super(FXML); + back.setVisible(false); + progressBar.setProgress(0); + progressPercent.setText("0.0%"); + progressBar.prefWidthProperty().bind(progressRoot.widthProperty().subtract(40)); + + //Load playing card + playingCard.addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + showPlayingCard(newValue); + } + }); + + //Show back face when flipped + flipped.addListener((observable, oldValue, newValue) -> { + if (newValue) { + back.setVisible(true); + instruction.setText("Did you get your answer right? Answer 'yes' or 'no'"); + } else { + back.setVisible(false); + instruction.setText("Type 'flip' to reveal back face"); + } + }); + + //Display new value and recalculate progress bar when number of Cards attempted changes + cardAttempted.addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + attempted = newValue; + setProgress(); + noAttempted.setText(String.valueOf(newValue)); + } + }); + + //Display new value and recalculate progress bar when number of Cards remaining changes + cardRemaining.addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + remaining = newValue; + setProgress(); + noRemaining.setText(String.valueOf(newValue)); + } + }); + } + + /** + * Shows current playing card on right panel. + * + * @param card card to be shown + */ + private void showPlayingCard(Card card) { + front.setText(card.getFrontFace().toString()); + front.setWrapText(true); + back.setText(card.getBackFace().toString()); + back.setWrapText(true); + instruction.setText("Type 'flip' to reveal back face"); + } + + /** + * Changes value of progress bar based on noOfCards attempted and remaining + */ + private void setProgress() { + double currentProgress = Double.valueOf(attempted) / (attempted + remaining); + double prog = currentProgress * 100; + progressPercent.setText(String.format("%.1f", prog) + "%"); + progressBar.setProgress(currentProgress); + } +} diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/com/flashspeed/ui/ResultDisplay.java similarity index 85% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/com/flashspeed/ui/ResultDisplay.java index 7d98e84eedf..a6bc1803b55 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/com/flashspeed/ui/ResultDisplay.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package com.flashspeed.ui; import static java.util.Objects.requireNonNull; @@ -7,7 +7,7 @@ import javafx.scene.layout.Region; /** - * A ui for the status bar that is displayed at the header of the application. + * A UI for the status bar that is displayed at the header of the application. */ public class ResultDisplay extends UiPart { @@ -24,5 +24,4 @@ public void setFeedbackToUser(String feedbackToUser) { requireNonNull(feedbackToUser); resultDisplay.setText(feedbackToUser); } - } diff --git a/src/main/java/com/flashspeed/ui/StatisticsPopUp.java b/src/main/java/com/flashspeed/ui/StatisticsPopUp.java new file mode 100644 index 00000000000..821ab65230f --- /dev/null +++ b/src/main/java/com/flashspeed/ui/StatisticsPopUp.java @@ -0,0 +1,90 @@ +package com.flashspeed.ui; + +import java.util.logging.Logger; + +import com.flashspeed.commons.core.LogsCenter; +import com.flashspeed.model.Statistics; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.stage.Stage; + +/** + * Controller for a statistics page + */ +public class StatisticsPopUp extends UiPart { + + private static final Logger logger = LogsCenter.getLogger(StatisticsPopUp.class); + private static final String FXML = "StatisticsPopUp.fxml"; + + + + @FXML + private Label stats; + + /** + * Creates a new HelpWindow. + * + * @param root Stage to use as the root of the HelpWindow. + */ + public StatisticsPopUp(Stage root, Statistics statistics) { + super(FXML, root); + if (statistics != null) { + stats.setText(statistics.toString()); + } + + } + + /** + * Creates a new HelpWindow. + */ + public StatisticsPopUp(Statistics statistics) { + this(new Stage(), statistics); + } + + + /** + * Shows the statistics window. + * @throws IllegalStateException + *
      + *
    • + * if this method is called on a thread other than the JavaFX Application Thread. + *
    • + *
    • + * if this method is called during animation or layout processing. + *
    • + *
    • + * if this method is called on the primary stage. + *
    • + *
    • + * if {@code dialogStage} is already showing. + *
    • + *
    + */ + public void show() { + logger.fine("Showing statistics of the previous play session"); + getRoot().show(); + getRoot().centerOnScreen(); + } + + /** + * Returns true if the statistics window is currently being shown. + */ + public boolean isShowing() { + return getRoot().isShowing(); + } + + /** + * Hides the statistics window. + */ + public void hide() { + getRoot().hide(); + } + + /** + * Focuses on the statistics window. + */ + public void focus() { + getRoot().requestFocus(); + } +} diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/com/flashspeed/ui/StatusBarFooter.java similarity index 79% rename from src/main/java/seedu/address/ui/StatusBarFooter.java rename to src/main/java/com/flashspeed/ui/StatusBarFooter.java index 7e17911323f..f4f1fe6d7f0 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/com/flashspeed/ui/StatusBarFooter.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package com.flashspeed.ui; import java.nio.file.Path; import java.nio.file.Paths; @@ -17,10 +17,8 @@ public class StatusBarFooter extends UiPart { @FXML private Label saveLocationStatus; - public StatusBarFooter(Path saveLocation) { super(FXML); - saveLocationStatus.setText(Paths.get(".").resolve(saveLocation).toString()); + saveLocationStatus.setText(Paths.get("Data saved to .").resolve(saveLocation).toString()); } - } diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/com/flashspeed/ui/Ui.java similarity index 85% rename from src/main/java/seedu/address/ui/Ui.java rename to src/main/java/com/flashspeed/ui/Ui.java index 17aa0b494fe..5afc7b88894 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/com/flashspeed/ui/Ui.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package com.flashspeed.ui; import javafx.stage.Stage; @@ -9,5 +9,4 @@ public interface Ui { /** Starts the UI (and the App). */ void start(Stage primaryStage); - } diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/com/flashspeed/ui/UiManager.java similarity index 77% rename from src/main/java/seedu/address/ui/UiManager.java rename to src/main/java/com/flashspeed/ui/UiManager.java index 876621d79b9..8f5b3995c8a 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/com/flashspeed/ui/UiManager.java @@ -1,16 +1,19 @@ -package seedu.address.ui; +package com.flashspeed.ui; import java.util.logging.Logger; +import com.flashspeed.MainApp; +import com.flashspeed.commons.core.LogsCenter; +import com.flashspeed.commons.util.StringUtil; +import com.flashspeed.logic.Logic; +import com.flashspeed.model.util.View; + import javafx.application.Platform; +import javafx.beans.value.ObservableValue; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.image.Image; import javafx.stage.Stage; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; /** * The manager of the UI component. @@ -20,14 +23,17 @@ public class UiManager implements Ui { public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/flashspeed_logo.png"; private Logic logic; private MainWindow mainWindow; + private ObservableValue view; + public UiManager(Logic logic) { super(); this.logic = logic; + this.view = logic.currentViewProperty(); } @Override @@ -39,9 +45,12 @@ public void start(Stage primaryStage) { try { mainWindow = new MainWindow(primaryStage, logic); - mainWindow.show(); //This should be called before creating other UI parts + mainWindow.show(); // this should be called before creating other UI parts mainWindow.fillInnerParts(); - + //listens for any changes in the current View to toggle between Play view and other views + this.view.addListener((observable, oldValue, newValue) -> { + mainWindow.fillInnerParts(); + }); } catch (Throwable e) { logger.severe(StringUtil.getDetails(e)); showFatalErrorDialogAndShutdown("Fatal error during initializing", e); @@ -82,5 +91,4 @@ private void showFatalErrorDialogAndShutdown(String title, Throwable e) { Platform.exit(); System.exit(1); } - } diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/com/flashspeed/ui/UiPart.java similarity index 97% rename from src/main/java/seedu/address/ui/UiPart.java rename to src/main/java/com/flashspeed/ui/UiPart.java index fc820e01a9c..007b85aa9a3 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/com/flashspeed/ui/UiPart.java @@ -1,12 +1,13 @@ -package seedu.address.ui; +package com.flashspeed.ui; import static java.util.Objects.requireNonNull; import java.io.IOException; import java.net.URL; +import com.flashspeed.MainApp; + import javafx.fxml.FXMLLoader; -import seedu.address.MainApp; /** * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc. @@ -84,5 +85,4 @@ private static URL getFxmlFileUrl(String fxmlFileName) { URL fxmlFileUrl = MainApp.class.getResource(fxmlFileNameWithFolder); return requireNonNull(fxmlFileUrl); } - } diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java deleted file mode 100644 index 1deb3a1e469..00000000000 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ /dev/null @@ -1,13 +0,0 @@ -package seedu.address.commons.core; - -/** - * Container for user visible messages. - */ -public class Messages { - - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; - public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - -} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java deleted file mode 100644 index 92cd8fa605a..00000000000 --- a/src/main/java/seedu/address/logic/Logic.java +++ /dev/null @@ -1,50 +0,0 @@ -package seedu.address.logic; - -import java.nio.file.Path; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; - -/** - * API of the Logic component - */ -public interface Logic { - /** - * Executes the command and returns the result. - * @param commandText The command as entered by the user. - * @return the result of the command execution. - * @throws CommandException If an error occurs during command execution. - * @throws ParseException If an error occurs during parsing. - */ - CommandResult execute(String commandText) throws CommandException, ParseException; - - /** - * Returns the AddressBook. - * - * @see seedu.address.model.Model#getAddressBook() - */ - ReadOnlyAddressBook getAddressBook(); - - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); - - /** - * Returns the user prefs' address book file path. - */ - Path getAddressBookFilePath(); - - /** - * Returns the user prefs' GUI settings. - */ - GuiSettings getGuiSettings(); - - /** - * Set the user prefs' GUI settings. - */ - void setGuiSettings(GuiSettings guiSettings); -} diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java deleted file mode 100644 index d47ce874b1a..00000000000 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ /dev/null @@ -1,78 +0,0 @@ -package seedu.address.logic; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; -import seedu.address.storage.Storage; - -/** - * The main LogicManager of the app. - */ -public class LogicManager implements Logic { - public static final String FILE_OPS_ERROR_MESSAGE = "Could not save data to file: "; - private final Logger logger = LogsCenter.getLogger(LogicManager.class); - - private final Model model; - private final Storage storage; - private final AddressBookParser addressBookParser; - - public LogicManager(Model model, Storage storage) { - this.model = model; - this.storage = storage; - addressBookParser = new AddressBookParser(); - } - - @Override - public CommandResult execute(String commandText) throws CommandException, ParseException { - logger.info("----------------[USER COMMAND][" + commandText + "]"); - - CommandResult commandResult; - Command command = addressBookParser.parseCommand(commandText); - commandResult = command.execute(model); - - try { - storage.saveAddressBook(model.getAddressBook()); - } catch (IOException ioe) { - throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); - } - - return commandResult; - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return model.getAddressBook(); - } - - @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); - } - - @Override - public Path getAddressBookFilePath() { - return model.getAddressBookFilePath(); - } - - @Override - public GuiSettings getGuiSettings() { - return model.getGuiSettings(); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - model.setGuiSettings(guiSettings); - } -} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index 71656d7c5c8..00000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; - - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java deleted file mode 100644 index 9c86b1fa6e4..00000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,23 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.model.AddressBook; -import seedu.address.model.Model; - -/** - * Clears the address book. - */ -public class ClearCommand extends Command { - - public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.setAddressBook(new AddressBook()); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index 02fd256acba..00000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index 7e36114902f..00000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,226 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -public class EditCommand extends Command { - - public static final String COMMAND_WORD = "edit"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - // state check - EditCommand e = (EditCommand) other; - return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional
    getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; - - return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index d6b19b0a0de..00000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. - */ -public class FindCommand extends Command { - - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 84be6ad2596..00000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.model.Model; - -/** - * Lists all persons in the address book to the user. - */ -public class ListCommand extends Command { - - public static final String COMMAND_WORD = "list"; - - public static final String MESSAGE_SUCCESS = "Listed all persons"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 3b8bfa035e8..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; -import java.util.stream.Stream; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new AddCommand object - */ -public class AddCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } - - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java deleted file mode 100644 index 1e466792b46..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,76 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses user input. - */ -public class AddressBookParser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - * @throws ParseException if the user input does not conform the expected format - */ - public Command parseCommand(String userInput) throws ParseException { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - default: - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java deleted file mode 100644 index 954c8e18f8e..00000000000 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.parser; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** - * Stores mapping of prefixes to their respective arguments. - * Each key may be associated with multiple argument values. - * Values for a given key are stored in a list, and the insertion ordering is maintained. - * Keys are unique, but the list of argument values may contain duplicate argument values, i.e. the same argument value - * can be inserted multiple times for the same prefix. - */ -public class ArgumentMultimap { - - /** Prefixes mapped to their respective arguments**/ - private final Map> argMultimap = new HashMap<>(); - - /** - * Associates the specified argument value with {@code prefix} key in this map. - * If the map previously contained a mapping for the key, the new value is appended to the list of existing values. - * - * @param prefix Prefix key with which the specified argument value is to be associated - * @param argValue Argument value to be associated with the specified prefix key - */ - public void put(Prefix prefix, String argValue) { - List argValues = getAllValues(prefix); - argValues.add(argValue); - argMultimap.put(prefix, argValues); - } - - /** - * Returns the last value of {@code prefix}. - */ - public Optional getValue(Prefix prefix) { - List values = getAllValues(prefix); - return values.isEmpty() ? Optional.empty() : Optional.of(values.get(values.size() - 1)); - } - - /** - * Returns all values of {@code prefix}. - * If the prefix does not exist or has no values, this will return an empty list. - * Modifying the returned list will not affect the underlying data structure of the ArgumentMultimap. - */ - public List getAllValues(Prefix prefix) { - if (!argMultimap.containsKey(prefix)) { - return new ArrayList<>(); - } - return new ArrayList<>(argMultimap.get(prefix)); - } - - /** - * Returns the preamble (text before the first valid prefix). Trims any leading/trailing spaces. - */ - public String getPreamble() { - return getValue(new Prefix("")).orElse(""); - } -} diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java deleted file mode 100644 index 5c9aebfa488..00000000000 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ /dev/null @@ -1,148 +0,0 @@ -package seedu.address.logic.parser; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -/** - * Tokenizes arguments string of the form: {@code preamble value value ...}
    - * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
    - * 1. An argument's value can be an empty string e.g. the value of {@code k/} in the above example.
    - * 2. Leading and trailing whitespaces of an argument value will be discarded.
    - * 3. An argument may be repeated and all its values will be accumulated e.g. the value of {@code t/} - * in the above example.
    - */ -public class ArgumentTokenizer { - - /** - * Tokenizes an arguments string and returns an {@code ArgumentMultimap} object that maps prefixes to their - * respective argument values. Only the given prefixes will be recognized in the arguments string. - * - * @param argsString Arguments string of the form: {@code preamble value value ...} - * @param prefixes Prefixes to tokenize the arguments string with - * @return ArgumentMultimap object that maps prefixes to their arguments - */ - public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) { - List positions = findAllPrefixPositions(argsString, prefixes); - return extractArguments(argsString, positions); - } - - /** - * Finds all zero-based prefix positions in the given arguments string. - * - * @param argsString Arguments string of the form: {@code preamble value value ...} - * @param prefixes Prefixes to find in the arguments string - * @return List of zero-based prefix positions in the given arguments string - */ - private static List findAllPrefixPositions(String argsString, Prefix... prefixes) { - return Arrays.stream(prefixes) - .flatMap(prefix -> findPrefixPositions(argsString, prefix).stream()) - .collect(Collectors.toList()); - } - - /** - * {@see findAllPrefixPositions} - */ - private static List findPrefixPositions(String argsString, Prefix prefix) { - List positions = new ArrayList<>(); - - int prefixPosition = findPrefixPosition(argsString, prefix.getPrefix(), 0); - while (prefixPosition != -1) { - PrefixPosition extendedPrefix = new PrefixPosition(prefix, prefixPosition); - positions.add(extendedPrefix); - prefixPosition = findPrefixPosition(argsString, prefix.getPrefix(), prefixPosition); - } - - return positions; - } - - /** - * Returns the index of the first occurrence of {@code prefix} in - * {@code argsString} starting from index {@code fromIndex}. An occurrence - * is valid if there is a whitespace before {@code prefix}. Returns -1 if no - * such occurrence can be found. - * - * E.g if {@code argsString} = "e/hip/900", {@code prefix} = "p/" and - * {@code fromIndex} = 0, this method returns -1 as there are no valid - * occurrences of "p/" with whitespace before it. However, if - * {@code argsString} = "e/hi p/900", {@code prefix} = "p/" and - * {@code fromIndex} = 0, this method returns 5. - */ - private static int findPrefixPosition(String argsString, String prefix, int fromIndex) { - int prefixIndex = argsString.indexOf(" " + prefix, fromIndex); - return prefixIndex == -1 ? -1 - : prefixIndex + 1; // +1 as offset for whitespace - } - - /** - * Extracts prefixes and their argument values, and returns an {@code ArgumentMultimap} object that maps the - * extracted prefixes to their respective arguments. Prefixes are extracted based on their zero-based positions in - * {@code argsString}. - * - * @param argsString Arguments string of the form: {@code preamble value value ...} - * @param prefixPositions Zero-based positions of all prefixes in {@code argsString} - * @return ArgumentMultimap object that maps prefixes to their arguments - */ - private static ArgumentMultimap extractArguments(String argsString, List prefixPositions) { - - // Sort by start position - prefixPositions.sort((prefix1, prefix2) -> prefix1.getStartPosition() - prefix2.getStartPosition()); - - // Insert a PrefixPosition to represent the preamble - PrefixPosition preambleMarker = new PrefixPosition(new Prefix(""), 0); - prefixPositions.add(0, preambleMarker); - - // Add a dummy PrefixPosition to represent the end of the string - PrefixPosition endPositionMarker = new PrefixPosition(new Prefix(""), argsString.length()); - prefixPositions.add(endPositionMarker); - - // Map prefixes to their argument values (if any) - ArgumentMultimap argMultimap = new ArgumentMultimap(); - for (int i = 0; i < prefixPositions.size() - 1; i++) { - // Extract and store prefixes and their arguments - Prefix argPrefix = prefixPositions.get(i).getPrefix(); - String argValue = extractArgumentValue(argsString, prefixPositions.get(i), prefixPositions.get(i + 1)); - argMultimap.put(argPrefix, argValue); - } - - return argMultimap; - } - - /** - * Returns the trimmed value of the argument in the arguments string specified by {@code currentPrefixPosition}. - * The end position of the value is determined by {@code nextPrefixPosition}. - */ - private static String extractArgumentValue(String argsString, - PrefixPosition currentPrefixPosition, - PrefixPosition nextPrefixPosition) { - Prefix prefix = currentPrefixPosition.getPrefix(); - - int valueStartPos = currentPrefixPosition.getStartPosition() + prefix.getPrefix().length(); - String value = argsString.substring(valueStartPos, nextPrefixPosition.getStartPosition()); - - return value.trim(); - } - - /** - * Represents a prefix's position in an arguments string. - */ - private static class PrefixPosition { - private int startPosition; - private final Prefix prefix; - - PrefixPosition(Prefix prefix, int startPosition) { - this.prefix = prefix; - this.startPosition = startPosition; - } - - int getStartPosition() { - return startPosition; - } - - Prefix getPrefix() { - return prefix; - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java deleted file mode 100644 index 75b1a9bf119..00000000000 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ /dev/null @@ -1,15 +0,0 @@ -package seedu.address.logic.parser; - -/** - * Contains Command Line Interface (CLI) syntax definitions common to multiple commands - */ -public class CliSyntax { - - /* Prefix definitions */ - public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); - public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); - -} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java deleted file mode 100644 index 522b93081cc..00000000000 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new DeleteCommand object - */ -public class DeleteCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand - * and returns a DeleteCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public DeleteCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java deleted file mode 100644 index 845644b7dea..00000000000 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ /dev/null @@ -1,82 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new EditCommand object - */ -public class EditCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public EditCommand parse(String args) throws ParseException { - requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - Index index; - - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); - } - - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); - } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); - } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); - } - - return new EditCommand(index, editPersonDescriptor); - } - - /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. - */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index 4fb71f23103..00000000000 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import java.util.Arrays; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Parses input arguments and creates a new FindCommand object - */ -public class FindCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns a FindCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - String[] nameKeywords = trimmedArgs.split("\\s+"); - - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java deleted file mode 100644 index b117acb9c55..00000000000 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ /dev/null @@ -1,124 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Contains utility methods used for parsing strings in the various *Parser classes. - */ -public class ParserUtil { - - public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; - - /** - * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be - * trimmed. - * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). - */ - public static Index parseIndex(String oneBasedIndex) throws ParseException { - String trimmedIndex = oneBasedIndex.trim(); - if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { - throw new ParseException(MESSAGE_INVALID_INDEX); - } - return Index.fromOneBased(Integer.parseInt(trimmedIndex)); - } - - /** - * Parses a {@code String name} into a {@code Name}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code name} is invalid. - */ - public static Name parseName(String name) throws ParseException { - requireNonNull(name); - String trimmedName = name.trim(); - if (!Name.isValidName(trimmedName)) { - throw new ParseException(Name.MESSAGE_CONSTRAINTS); - } - return new Name(trimmedName); - } - - /** - * Parses a {@code String phone} into a {@code Phone}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code phone} is invalid. - */ - public static Phone parsePhone(String phone) throws ParseException { - requireNonNull(phone); - String trimmedPhone = phone.trim(); - if (!Phone.isValidPhone(trimmedPhone)) { - throw new ParseException(Phone.MESSAGE_CONSTRAINTS); - } - return new Phone(trimmedPhone); - } - - /** - * Parses a {@code String address} into an {@code Address}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code address} is invalid. - */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_CONSTRAINTS); - } - return new Address(trimmedAddress); - } - - /** - * Parses a {@code String email} into an {@code Email}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code email} is invalid. - */ - public static Email parseEmail(String email) throws ParseException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new ParseException(Email.MESSAGE_CONSTRAINTS); - } - return new Email(trimmedEmail); - } - - /** - * Parses a {@code String tag} into a {@code Tag}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code tag} is invalid. - */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(trimmedTag); - } - - /** - * Parses {@code Collection tags} into a {@code Set}. - */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); - } - return tagSet; - } -} diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/seedu/address/logic/parser/Prefix.java deleted file mode 100644 index c859d5fa5db..00000000000 --- a/src/main/java/seedu/address/logic/parser/Prefix.java +++ /dev/null @@ -1,39 +0,0 @@ -package seedu.address.logic.parser; - -/** - * A prefix that marks the beginning of an argument in an arguments string. - * E.g. 't/' in 'add James t/ friend'. - */ -public class Prefix { - private final String prefix; - - public Prefix(String prefix) { - this.prefix = prefix; - } - - public String getPrefix() { - return prefix; - } - - public String toString() { - return getPrefix(); - } - - @Override - public int hashCode() { - return prefix == null ? 0 : prefix.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof Prefix)) { - return false; - } - if (obj == this) { - return true; - } - - Prefix otherPrefix = (Prefix) obj; - return otherPrefix.getPrefix().equals(getPrefix()); - } -} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 1a943a0781a..00000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - - /* - * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html - * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. - */ - { - persons = new UniquePersonList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - this.persons.setPersons(persons); - } - - /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. - */ - public void resetData(ReadOnlyAddressBook newData) { - requireNonNull(newData); - - setPersons(newData.getPersonList()); - } - - //// person-level operations - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); - } - - /** - * Adds a person to the address book. - * The person must not already exist in the address book. - */ - public void addPerson(Person p) { - persons.add(p); - } - - /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); - - persons.setPerson(target, editedPerson); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. - */ - public void removePerson(Person key) { - persons.remove(key); - } - - //// util methods - - @Override - public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; - // TODO: refine later - } - - @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); - } - - @Override - public int hashCode() { - return persons.hashCode(); - } -} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java deleted file mode 100644 index d54df471c1f..00000000000 --- a/src/main/java/seedu/address/model/Model.java +++ /dev/null @@ -1,87 +0,0 @@ -package seedu.address.model; - -import java.nio.file.Path; -import java.util.function.Predicate; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; - -/** - * The API of the Model component. - */ -public interface Model { - /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; - - /** - * Replaces user prefs data with the data in {@code userPrefs}. - */ - void setUserPrefs(ReadOnlyUserPrefs userPrefs); - - /** - * Returns the user prefs. - */ - ReadOnlyUserPrefs getUserPrefs(); - - /** - * Returns the user prefs' GUI settings. - */ - GuiSettings getGuiSettings(); - - /** - * Sets the user prefs' GUI settings. - */ - void setGuiSettings(GuiSettings guiSettings); - - /** - * Returns the user prefs' address book file path. - */ - Path getAddressBookFilePath(); - - /** - * Sets the user prefs' address book file path. - */ - void setAddressBookFilePath(Path addressBookFilePath); - - /** - * Replaces address book data with the data in {@code addressBook}. - */ - void setAddressBook(ReadOnlyAddressBook addressBook); - - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - boolean hasPerson(Person person); - - /** - * Deletes the given person. - * The person must exist in the address book. - */ - void deletePerson(Person target); - - /** - * Adds the given person. - * {@code person} must not already exist in the address book. - */ - void addPerson(Person person); - - /** - * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - void setPerson(Person target, Person editedPerson); - - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); - - /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. - * @throws NullPointerException if {@code predicate} is null. - */ - void updateFilteredPersonList(Predicate predicate); -} diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java deleted file mode 100644 index 0650c954f5c..00000000000 --- a/src/main/java/seedu/address/model/ModelManager.java +++ /dev/null @@ -1,151 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.nio.file.Path; -import java.util.function.Predicate; -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.collections.transformation.FilteredList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Represents the in-memory model of the address book data. - */ -public class ModelManager implements Model { - private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - - private final AddressBook addressBook; - private final UserPrefs userPrefs; - private final FilteredList filteredPersons; - - /** - * Initializes a ModelManager with the given addressBook and userPrefs. - */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { - super(); - requireAllNonNull(addressBook, userPrefs); - - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); - - this.addressBook = new AddressBook(addressBook); - this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); - } - - public ModelManager() { - this(new AddressBook(), new UserPrefs()); - } - - //=========== UserPrefs ================================================================================== - - @Override - public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { - requireNonNull(userPrefs); - this.userPrefs.resetData(userPrefs); - } - - @Override - public ReadOnlyUserPrefs getUserPrefs() { - return userPrefs; - } - - @Override - public GuiSettings getGuiSettings() { - return userPrefs.getGuiSettings(); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - requireNonNull(guiSettings); - userPrefs.setGuiSettings(guiSettings); - } - - @Override - public Path getAddressBookFilePath() { - return userPrefs.getAddressBookFilePath(); - } - - @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - userPrefs.setAddressBookFilePath(addressBookFilePath); - } - - //=========== AddressBook ================================================================================ - - @Override - public void setAddressBook(ReadOnlyAddressBook addressBook) { - this.addressBook.resetData(addressBook); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; - } - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return addressBook.hasPerson(person); - } - - @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); - } - - @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - } - - @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - addressBook.setPerson(target, editedPerson); - } - - //=========== Filtered Person List Accessors ============================================================= - - /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} - */ - @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; - } - - @Override - public void updateFilteredPersonList(Predicate predicate) { - requireNonNull(predicate); - filteredPersons.setPredicate(predicate); - } - - @Override - public boolean equals(Object obj) { - // short circuit if same object - if (obj == this) { - return true; - } - - // instanceof handles nulls - if (!(obj instanceof ModelManager)) { - return false; - } - - // state check - ModelManager other = (ModelManager) obj; - return addressBook.equals(other.addressBook) - && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); - } - -} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java deleted file mode 100644 index 6ddc2cd9a29..00000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,17 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. - */ - ObservableList getPersonList(); - -} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index 60472ca22a0..00000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,57 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java deleted file mode 100644 index a5bbe0b6a5f..00000000000 --- a/src/main/java/seedu/address/model/person/Email.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's email in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} - */ -public class Email { - - private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-"; - public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain " - + "and adhere to the following constraints:\n" - + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " - + "the parentheses, (" + SPECIAL_CHARACTERS + ") .\n" - + "2. This is followed by a '@' and then a domain name. " - + "The domain name must:\n" - + " - be at least 2 characters long\n" - + " - start and end with alphanumeric characters\n" - + " - consist of alphanumeric characters, a period or a hyphen for the characters in between, if any."; - // alphanumeric and special characters - private static final String LOCAL_PART_REGEX = "^[\\w" + SPECIAL_CHARACTERS + "]+"; - private static final String DOMAIN_FIRST_CHARACTER_REGEX = "[^\\W_]"; // alphanumeric characters except underscore - private static final String DOMAIN_MIDDLE_REGEX = "[a-zA-Z0-9.-]*"; // alphanumeric, period and hyphen - private static final String DOMAIN_LAST_CHARACTER_REGEX = "[^\\W_]$"; - public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" - + DOMAIN_FIRST_CHARACTER_REGEX + DOMAIN_MIDDLE_REGEX + DOMAIN_LAST_CHARACTER_REGEX; - - public final String value; - - /** - * Constructs an {@code Email}. - * - * @param email A valid email address. - */ - public Email(String email) { - requireNonNull(email); - checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS); - value = email; - } - - /** - * Returns if a given string is a valid email. - */ - public static boolean isValidEmail(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Email // instanceof handles nulls - && value.equals(((Email) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java deleted file mode 100644 index 79244d71cf7..00000000000 --- a/src/main/java/seedu/address/model/person/Name.java +++ /dev/null @@ -1,59 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's name in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} - */ -public class Name { - - public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; - - public final String fullName; - - /** - * Constructs a {@code Name}. - * - * @param name A valid name. - */ - public Name(String name) { - requireNonNull(name); - checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); - fullName = name; - } - - /** - * Returns true if a given string is a valid name. - */ - public static boolean isValidName(String test) { - return test.matches(VALIDATION_REGEX); - } - - - @Override - public String toString() { - return fullName; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Name // instanceof handles nulls - && fullName.equals(((Name) other).fullName)); // state check - } - - @Override - public int hashCode() { - return fullName.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java deleted file mode 100644 index c9b5868427c..00000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate { - private final List keywords; - - public NameContainsKeywordsPredicate(List keywords) { - this.keywords = keywords; - } - - @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 557a7a60cd5..00000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - - /** - * Returns true if both persons of the same name have at least one other identity field that is the same. - * This defines a weaker notion of equality between two persons. - */ - public boolean isSamePerson(Person otherPerson) { - if (otherPerson == this) { - return true; - } - - return otherPerson != null - && otherPerson.getName().equals(getName()) - && (otherPerson.getPhone().equals(getPhone()) || otherPerson.getEmail().equals(getEmail())); - } - - /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof Person)) { - return false; - } - - Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) - && otherPerson.getPhone().equals(getPhone()) - && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append(" Phone: ") - .append(getPhone()) - .append(" Email: ") - .append(getEmail()) - .append(" Address: ") - .append(getAddress()) - .append(" Tags: "); - getTags().forEach(builder::append); - return builder.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java deleted file mode 100644 index 872c76b382f..00000000000 --- a/src/main/java/seedu/address/model/person/Phone.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's phone number in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} - */ -public class Phone { - - - public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; - public static final String VALIDATION_REGEX = "\\d{3,}"; - public final String value; - - /** - * Constructs a {@code Phone}. - * - * @param phone A valid phone number. - */ - public Phone(String phone) { - requireNonNull(phone); - checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS); - value = phone; - } - - /** - * Returns true if a given string is a valid phone number. - */ - public static boolean isValidPhone(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Phone // instanceof handles nulls - && value.equals(((Phone) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index 0fee4fe57e6..00000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,137 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Iterator; -import java.util.List; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so - * as to ensure that the person with exactly the same fields will be removed. - * - * Supports a minimal set of list operations. - * - * @see Person#isSamePerson(Person) - */ -public class UniquePersonList implements Iterable { - - private final ObservableList internalList = FXCollections.observableArrayList(); - private final ObservableList internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); - } - - /** - * Adds a person to the list. - * The person must not already exist in the list. - */ - public void add(Person toAdd) { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * The person must exist in the list. - */ - public void remove(Person toRemove) { - requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); - } - } - - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - - /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return internalUnmodifiableList; - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - /** - * Returns true if {@code persons} contains only unique persons. - */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java deleted file mode 100644 index d7290f59442..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,11 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same - * identity). - */ -public class DuplicatePersonException extends RuntimeException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index fa764426ca7..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java deleted file mode 100644 index b0ea7e7dad7..00000000000 --- 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_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String 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_CONSTRAINTS); - this.tagName = tagName; - } - - /** - * Returns true if a given string is a valid tag name. - */ - public static boolean isValidTagName(String test) { - return test.matches(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 1806da4facf..00000000000 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.model.util; - -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Contains utility methods for populating {@code AddressBook} with sample data. - */ -public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) - }; - } - - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); - } - return sampleAb; - } - - /** - * Returns a tag set containing the list of strings given. - */ - public static Set getTagSet(String... strings) { - return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); - } - -} diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index 4599182b3f9..00000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - Path getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional readAddressBook() throws DataConversionException, IOException; - - /** - * @see #getAddressBookFilePath() - */ - Optional readAddressBook(Path filePath) throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java deleted file mode 100644 index a6321cec2ea..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Person}. - */ -class JsonAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - private final String name; - private final String phone; - private final String email; - private final String address; - private final List tagged = new ArrayList<>(); - - /** - * Constructs a {@code JsonAdaptedPerson} with the given person details. - */ - @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged.addAll(tagged); - } - } - - /** - * Converts a given {@code Person} into this class for Jackson use. - */ - public JsonAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList())); - } - - /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person. - */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); - } - - if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); - } - if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); - } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java deleted file mode 100644 index 0df22bdb754..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ /dev/null @@ -1,48 +0,0 @@ -package seedu.address.storage; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Tag}. - */ -class JsonAdaptedTag { - - private final String tagName; - - /** - * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}. - */ - @JsonCreator - public JsonAdaptedTag(String tagName) { - this.tagName = tagName; - } - - /** - * Converts a given {@code Tag} into this class for Jackson use. - */ - public JsonAdaptedTag(Tag source) { - tagName = source.tagName; - } - - @JsonValue - public String getTagName() { - return tagName; - } - - /** - * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted tag. - */ - public Tag toModelType() throws IllegalValueException { - if (!Tag.isValidTagName(tagName)) { - throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(tagName); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java deleted file mode 100644 index dfab9daaa0d..00000000000 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ /dev/null @@ -1,80 +0,0 @@ -package seedu.address.storage; - -import static java.util.Objects.requireNonNull; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * A class to access AddressBook data stored as a json file on the hard disk. - */ -public class JsonAddressBookStorage implements AddressBookStorage { - - private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class); - - private Path filePath; - - public JsonAddressBookStorage(Path filePath) { - this.filePath = filePath; - } - - public Path getAddressBookFilePath() { - return filePath; - } - - @Override - public Optional readAddressBook() throws DataConversionException { - return readAddressBook(filePath); - } - - /** - * Similar to {@link #readAddressBook()}. - * - * @param filePath location of the data. Cannot be null. - * @throws DataConversionException if the file is not in the correct format. - */ - public Optional readAddressBook(Path filePath) throws DataConversionException { - requireNonNull(filePath); - - Optional jsonAddressBook = JsonUtil.readJsonFile( - filePath, JsonSerializableAddressBook.class); - if (!jsonAddressBook.isPresent()) { - return Optional.empty(); - } - - try { - return Optional.of(jsonAddressBook.get().toModelType()); - } catch (IllegalValueException ive) { - logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); - throw new DataConversionException(ive); - } - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); - } - - /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}. - * - * @param filePath location of the data. Cannot be null. - */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - requireNonNull(addressBook); - requireNonNull(filePath); - - FileUtil.createIfMissing(filePath); - JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java deleted file mode 100644 index 5efd834091d..00000000000 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonRootName; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; - -/** - * An Immutable AddressBook that is serializable to JSON format. - */ -@JsonRootName(value = "addressbook") -class JsonSerializableAddressBook { - - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; - - private final List persons = new ArrayList<>(); - - /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. - */ - @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); - } - - /** - * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. - * - * @param source future changes to this will not affect the created {@code JsonSerializableAddressBook}. - */ - public JsonSerializableAddressBook(ReadOnlyAddressBook source) { - persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList())); - } - - /** - * Converts this address book into the model's {@code AddressBook} object. - * - * @throws IllegalValueException if there were any data constraints violated. - */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); - for (JsonAdaptedPerson jsonAdaptedPerson : persons) { - Person person = jsonAdaptedPerson.toModelType(); - if (addressBook.hasPerson(person)) { - throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); - } - addressBook.addPerson(person); - } - return addressBook; - } - -} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java deleted file mode 100644 index beda8bd9f11..00000000000 --- a/src/main/java/seedu/address/storage/Storage.java +++ /dev/null @@ -1,32 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; - -/** - * API of the Storage component - */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { - - @Override - Optional readUserPrefs() throws DataConversionException, IOException; - - @Override - void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; - - @Override - Path getAddressBookFilePath(); - - @Override - Optional readAddressBook() throws DataConversionException, IOException; - - @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java deleted file mode 100644 index e4f452b6cbf..00000000000 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ /dev/null @@ -1,77 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; - -/** - * Manages storage of AddressBook data in local storage. - */ -public class StorageManager implements Storage { - - private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; - private UserPrefsStorage userPrefsStorage; - - - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { - super(); - this.addressBookStorage = addressBookStorage; - this.userPrefsStorage = userPrefsStorage; - } - - // ================ UserPrefs methods ============================== - - @Override - public Path getUserPrefsFilePath() { - return userPrefsStorage.getUserPrefsFilePath(); - } - - @Override - public Optional readUserPrefs() throws DataConversionException, IOException { - return userPrefsStorage.readUserPrefs(); - } - - @Override - public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { - userPrefsStorage.saveUserPrefs(userPrefs); - } - - - // ================ AddressBook methods ============================== - - @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); - } - - @Override - public Optional readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); - } - - @Override - public Optional readAddressBook(Path filePath) throws DataConversionException, IOException { - logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); - } - -} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index 0684b088868..00000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,74 +0,0 @@ -package seedu.address.ui; - -import java.util.Comparator; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import seedu.address.model.person.Person; - -/** - * An UI component that displays information of a {@code Person}. - */ -public class PersonCard extends UiPart { - - private static final String FXML = "PersonListCard.fxml"; - - /** - * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. - * As a consequence, UI elements' variable names cannot be set to such keywords - * or an exception will be thrown by JavaFX during runtime. - * - * @see The issue on AddressBook level 4 - */ - - public final Person person; - - @FXML - private HBox cardPane; - @FXML - private Label name; - @FXML - private Label id; - @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private FlowPane tags; - - public PersonCard(Person person, int displayedIndex) { - super(FXML); - this.person = person; - id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof PersonCard)) { - return false; - } - - // state check - PersonCard card = (PersonCard) other; - return id.getText().equals(card.id.getText()) - && person.equals(card.person); - } -} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index 1328917096e..00000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,46 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView personListView; - - public PersonListPanel(ObservableList personList) { - super(FXML); - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/resources/docs/HelpWindow.html b/src/main/resources/docs/HelpWindow.html new file mode 100644 index 00000000000..cacb3e59117 --- /dev/null +++ b/src/main/resources/docs/HelpWindow.html @@ -0,0 +1,1692 @@ + + + + + + + + + FlashSpeed - User Guide + + + + + +
    + +
    +

    1. Introduction

    +
    +
    +

    FlashSpeed is a text-based flashcard application specifically designed for university students who are learning a foreign language. University students often have hectic schedules. With this in mind, FlashSpeed was created to allow students to be able to study and revise foreign vocabulary on the fly. With a single command, you can start a quick study session on FlashSpeed whenever!

    +
    +
    +

    Keeping, flipping, and tracking physical flashcards can be a pain. FlashSpeed enhances the studying process by having virtual flashcards and a smarter review system. Users will be tested more frequently on flashcards that they had trouble memorizing previously. By using FlashSpeed, you will learn faster and remember for longer!

    +
    +
    +

    This guide will walk you through the many exciting features of FlashSpeed as well as give step-by-step instructions on how to use them. Helpful tips and notes are also provided!

    +
    +
    +

    We hope you enjoy using our app. Happy FlashSpeeding!

    +
    +
    +
    +
    +

    2. Language Support

    +
    +
    +

    FlashSpeed can support almost every popular language and their associated script. You can even use emojis in your cards! 😍

    +
    +
    +

    You can use FlashSpeed to study:

    +
    +
    +
      +
    • +

      Japanese

      +
    • +
    • +

      Korean

      +
    • +
    • +

      French

      +
    • +
    • +

      Spanish

      +
    • +
    • +

      German

      +
    • +
    • +

      Hindi

      +
    • +
    • +

      Russian

      +
    • +
    • +

      …​and many more!

      +
    • +
    +
    +
    + + + + + +
    +
    Note
    +
    + The display of the characters may be limited to your own operating systems’s language support. Please refer to + your operating system’s manual or user guide to find out the languages and scripts supported. +
    +
    +
    + + + + + +
    +
    Note
    +
    + Technical info: FlashSpeed, which is built on Java, supports the Unicode standard with UTF-16 character encoding for + textual representation. +
    +
    +
    +
    +
    +

    3. Quick Start

    +
    +
    +

    Getting FlashSpeed up and running is fast and easy. Follow these simple steps to get started!

    +
    +
    +
      +
    1. +

      Ensure you have Java 11 or above installed in your computer.

      +
    2. +
    3. +

      Download the latest flashspeed.jar file here. This is the entire FlashSpeed app in one convenient package.

      +
    4. +
    5. +

      Copy flashspeed.jar to a folder of your choice. This will be FlashSpeed’s home folder. All files created by FlashSpeed will be automatically stored here.

      +
    6. +
    7. +

      Double-click flashspeed.jar to start it. FlashSpeed will appear in a few seconds.

      +
    8. +
    9. +

      Type a command in the command box and press Enter to execute it.
      + e.g. typing help and pressing Enter will open the Help window.

      +
    10. +
    11. +

      Some example commands you can try:

      +
      +
        +
      • +

        createJapanese 1 : creates a deck named Japanese 1 in the library

        +
      • +
      • +

        remove3 : removes the 3rd deck shown in the decks list

        +
      • +
      • +

        exit : exits the app

        +
      • +
      +
      +
    12. +
    13. +

      Refer to Section 4, “Features” for details of each command or Section 6, “Command Summary” for a condensed view of all commands.

      +
    14. +
    +
    +
    +
    +
    +

    4. Features

    +
    +
    +
    +
    +

    Command Format

    +
    +
    +
      +
    • +

      Words in <> are the parameters to be supplied by the user.
      + e.g. in create <deck>, <deck> is a parameter which can be used such as create Japanese 1.

      +
    • +
    +
    +
    +
    +
    +
    +
    +

    Different Views

    +
    +
    + + + + + +
    +
    Note
    +
    + Some commands only work in certain views, but don’t worry, this guide will tell you all you need to know! +
    +
    +
    +

    FlashSpeed can be in one of 3 different views, namely:

    +
    +
    +
      +
    • +

      Library view: when no deck is selected and no cards are shown

      +
    • +
    +
    +
    +
    + library +
    +
    Figure 1. In Library view. No deck is selected.
    +
    +
    +


    +
    +
    +
      +
    • +

      Deck view: when a deck is selected and its cards are shown

      +
    • +
    +
    +
    +
    + Ui +
    +
    Figure 2. In Deck view. A deck has been selected.
    +
    +
    +


    +
    +
    +
      +
    • +

      Play view: when in a study session of a deck

      +
    • +
    +
    +
    +
    + Ui2 +
    +
    Figure 3. In Play view. A deck is being studied.
    +
    +
    +
    +
    +


    +
    +
    +

    4.1. General

    +
    +

    4.1.1. Viewing help : help

    +
    +

    Format: help

    +
    +
    +

    You can view this user guide in a new window by + typing help in the input box and pressing Enter.

    +
    +
    + + + + + +
    +
    Tip
    +
    + Other than clicking on the close button, you can also press Alt+F4 to close the help window. +
    +
    +
    +


    +
    +
    +
    +

    4.1.2. Exiting the program : exit

    +
    +

    Format: exit

    +
    +
    +

    You can exit FlashSpeed by typing exit in the input box + and pressing Enter. Bye!

    +
    +
    +


    +
    +
    +
    +

    4.1.3. Resetting the library : reset

    +
    +

    Format: reset

    +
    +
    +

    If you want to delete everything in the library to start fresh, you can choose to reset the library. + You can do so by typing reset in the input and pressing Enter.

    +
    +
    +
    + reset +
    +
    Figure 4. After resetting. A new start!
    +
    +
    +


    +
    +
    +
    +
    +

    4.2. Library view

    +
    +
    +
    + + + + + +
    +
    Note
    +
    + Even though the commands in this section are mainly library based, you can use them in both the Library view and Deck view! +
    +
    +
    +
    + library +
    +
    Figure 5. No deck selected. You’re now in Library view.
    +
    +
    +
    +
    +


    +
    +
    +

    4.2.1. Creating a deck : create

    +
    +

    Format: create <deck>

    +
    +
    +
    +
    +
      +
    • +

      Formal definition: Creates a deck with the deck name specified by the user. The deck name cannot be empty.

      +
    • +
    +
    +
    +
    +
    +

    After downloading and setting up FlashSpeed, you’re all set to go! + But before anything else, you will have to first create a deck. + The process of creating a deck in FlashSpeed is very simple.

    +
    +
    +

    Let’s say you want to create a deck to revise some Japanese verbs + to prepare for you upcoming Japanese test. + To do so:

    +
    +
    +
      +
    1. +

      Firstly, type create followed by the deck name into the input box.

      +
      +
        +
      • +

        e.g. create Japanese Verbs

        +
        +
        + create1 +
        +
        Figure 6. Typing in the command to create the Japanese Verbs deck.
        +
        +
      • +
      +
      +
    2. +
    3. +

      Press Enter.

      +
    4. +
    5. +

      Voila!

      +
      +
      + create2 +
      +
      Figure 7. After creating the Japanese Verbs deck.
      +
      +
    6. +
    +
    +
    +


    +
    +
    +
    +

    4.2.2. Selecting a deck : select

    +
    +

    Format: select <index>

    +
    +
    +
    +
    +
      +
    • +

      Formal definition: + Selects and shows all cards in the deck at the specified index. + The index refers to the index number shown in the displayed decks list. The index must be a positive integer 1, 2, 3, …​

      +
    • +
    +
    +
    +
    +
    +

    If you want to view the cards in a particular deck, you can use select followed by the deck’s index number. + The index number is the number to the left of its name! + After selecting a certain deck, FlashSpeed will enter the Deck view and + show all the cards in that deck.

    +
    +
    +

    In the Deck view, you can use any of the Deck mode commands listed in 3.3. Deck view.

    +
    +
    +

    Example:

    +
    +
    +
      +
    • +

      select 1
      + Selects and shows all cards in the 1st deck.

      +
    • +
    +
    +
    +
    + Ui +
    +
    Figure 8. Selecting a deck and showing its cards.
    +
    +
    +


    +
    +
    +
    +

    4.2.3. Removing a deck : remove

    +
    +

    Format: remove <index>

    +
    +
    +
    +
    +
      +
    • +

      Formal definition: Removes the deck in the library at the specified index. + The index refers to the index number shown in the displayed decks list. + The index must be a positive integer 1, 2, 3, …​

      +
    • +
    +
    +
    +
    +
    +

    Similarly, you can remove a deck in the library using remove followed by the index number of that deck. Bye bye, deck!

    +
    +
    +

    Examples:

    +
    +
    +
      +
    • +

      remove 2
      + Removes the 2nd deck from the library.

      +
    • +
    +
    +
    +
    + remove +
    +
    Figure 9. After removing the Japanese Verbs deck.
    +
    +
    +


    +
    +
    +
    +

    4.2.4. Renaming a deck : rename

    +
    +

    Format: rename <index> <deck>

    +
    +
    +
    +
    +
      +
    • +

      Formal definition: Renames the deck in the library at the specified index. The index refers to the index number shown in the displayed decks list. The index must be a positive integer 1, 2, 3, …​

      +
    • +
    +
    +
    +
    +
    +

    You can rename a deck in the library using rename followed by the index number of the deck and the new name of the deck.

    +
    +
    + + + + + +
    +
    Note
    +
    + The deck name cannot by empty. +
    +
    +
    +

    Examples:

    +
    +
    +
      +
    • +

      rename 2 Japanese Verbs
      + Renames the 2nd deck in the library to "Japanese Verbs".

      +
    • +
    +
    +
    +
    + rename +
    +
    Figure 10. After renaming a deck from "Korean" to "Japanese Verbs". Wow.
    +
    +
    +


    +
    +
    +
    +

    4.2.5. Playing a deck : play

    +
    +

    Format: play <index>

    +
    +
    +
    +
    +
      +
    • +

      Formal definition: Starts a study session with the deck in the library at the specified index. The index refers to the index number shown in the displayed decks list. The index must be a positive integer 1, 2, 3, …​

      +
    • +
    +
    +
    +
    +
    +

    Alright, this may be what you’ve been waiting for! In order to start a study session with a certain deck, + you can use play followed by the deck’s index number. Good luck on your learning journey.
    + FYI: we "play" a deck because learning is fun!

    +
    +
    +

    Example:

    +
    +
    +
      +
    • +

      play 1
      + Starts a study session with the first deck in the library.

      +
    • +
    +
    +
    +
    + Ui2 +
    +
    Figure 11. Studying/Playing the Japanese deck.
    +
    +
    +


    +
    +
    +
    +
    +

    4.3. Deck view

    +
    +
    +
    + + + + + +
    +
    Note
    +
    + Important: All commands in this section can only be used in Deck view. + To enter Deck view, you must select a deck. +
    +
    +
    +
    + Ui +
    +
    Figure 12. Selecting a deck. You’re now in Deck view.
    +
    +
    +
    +
    +


    +
    +
    +

    4.3.1. Adding a card : add

    +
    +

    Format: add <front>:<back>

    +
    +
    +
    +
    +
      +
    • +

      Formal definition: Creates a card and adds it to a deck, + with the front and back values of the card being specified by the user. + Both the front and back values cannot be empty.

      +
    • +
    +
    +
    +
    +
    +
    +
    +
      +
    • +

      One and only one colon (":") can be used in this command. + Since a colon is used as the separator, there should not be any colons in the front or back values.

      +
    • +
    +
    +
    +
    +
    +

    Alright, after creating a new deck and giving it a great name, what’s next? + Adding cards into that deck, of course!

    +
    +
    +

    Once again, the process is easy:

    +
    +
    +
      +
    1. +

      Select the deck to which you want to add cards, with the select command.

      +
      +

      e.g. select 1

      +
      +
    2. +
    3. +

      Inside the input box, type:

      +
      +
        +
      • +

        add, followed by

        +
      • +
      • +

        the word/sentence that you want as the front of the card, then

        +
      • +
      • +

        a colon ":" right after, and finally

        +
      • +
      • +

        the word/sentence that you want as the back of the card.

        +
        +

        e.g. add ありがとう:thanks

        +
        +
      • +
      +
      +
    4. +
    5. +

      Press Enter.

      +
    6. +
    7. +

      Voila!

      +
      +
      + add +
      +
      Figure 13. Adding a new card into the Japanese deck.
      +
      +
    8. +
    +
    +
    +


    +
    +
    +
    +

    4.3.2. Editing a card : edit

    +
    +

    Format 1: edit <index> <front>:<back>
    + Format 2: edit <index> :<back>
    + Format 3: edit <index> <front>:

    +
    +
    +
    +
    +
      +
    • +

      Formal definition: Edits the card in the current deck at the specified index. The index refers to the index number shown in the displayed cards list. The index must be a positive integer 1, 2, 3, …​

      +
    • +
    • +

      Existing values will be updated to the given values.

      +
    • +
    • +

      Empty values for the front or back will leave the original front or back intact.

      +
    • +
    +
    +
    +
    +
    +
    +
    +
      +
    • +

      One and only one colon (":") can be used in this command. + Since a colon is used as the separator, there should not be any colons in the front or back values.

      +
    • +
    +
    +
    +
    +
    +

    You can edit the front and/or back values of any card in the current deck.

    +
    +
    + + + + + +
    +
    Tip
    +
    + You don’t have to supply both the front and back values of a card if you only want to change one of them, + e.g. if you only wish to change the back of a card, front can be left blank, and vice versa. +
    +
    +
    +

    Examples:

    +
    +
    +
      +
    • +

      edit 2 ありがとう:thanks
      + Edits the front and back values of the 1st card in the deck to be ありがとう and thanks respectively.

      +
    • +
    • +

      edit 2 :thanks
      + Edits the back value of the 1st card in the deck to be thanks.

      +
    • +
    • +

      edit 2 ありがとう:
      + Edits the front value of the 1st card in the deck to be ありがとう.

      +
    • +
    +
    +
    +
    + edit +
    +
    Figure 14. Editing the second card’s back value from "thank you" to "thanks".
    +
    +
    +


    +
    +
    +
    +

    4.3.3. Deleting a card : delete

    +
    +

    Format: delete <index>

    +
    +
    +
    +
    +
      +
    • +

      Formal definition: Deletes the card in the current deck at the specified index. The index refers to the index number shown in the displayed cards list. The index must be a positive integer 1, 2, 3, …​

      +
    • +
    +
    +
    +
    +
    +

    You can delete a card from the selected deck by using delete followed by the index number of that card.

    +
    +
    +

    Examples:

    +
    +
    +
      +
    • +

      delete 3
      + Deletes the 3rd card in the current deck.

      +
    • +
    +
    +
    +
    + delete +
    +
    Figure 15. After deleting the 3rd card in the selected deck.
    +
    +
    +


    +
    +
    +
    +

    4.3.4. Returning to the library : return

    +
    +

    Format: return

    +
    +
    +

    Once you’re done modifying the selected deck, + you can return back to the Library view with return, i.e. no deck will be selected.

    +
    +
    +
    + library +
    +
    Figure 16. Returned to the Library view. No deck selected!
    +
    +
    +


    +
    +
    +
    +
    +

    4.4. Play view

    +
    +

    Format: play <index>

    +
    +
    +
    +
    + + + + + +
    +
    Note
    +
    + Important: All commands in this section can only be used in Play view. + To enter Play view, you must play a deck. +
    +
    +
    +

    Time to start testing yourself! You can start a game session with a selected deck when you want to test how well you memorize the cards or just to enhance + your memory of the cards in a specific deck. When playing a deck, each card will initially show only its front face to allow you to recall its back face. (It’s to test you, after all!)

    +
    +
    +
    + Ui2 +
    +
    Figure 17. Playing the Japanese deck. Ganbatte kudasai!
    +
    +
    +
    +
    +


    +
    +
    +

    4.4.1. Flipping a card : flip

    +
    +

    Format: flip

    +
    +
    +

    Are you ready? When you have a guess which you are confident with of the back face of the card, try to flip the card to see + if your guess is correct. To flip a card to see its back face, simply type flip into the input box + and press Enter.

    +
    +
    +
    + flip +
    +
    Figure 18. Flipping to reveal the back face of the card.
    +
    +
    +


    +
    +
    +
    +

    4.4.2. Answering "yes" or "no" : yes/no

    +
    +

    Format: yes or no

    +
    +
    +

    Are you able to recall the back face of the card?
    + If you could, type yes into the input box and press Enter. (Congratulations!)
    + If you could not or your guess is wrong, type no into the input box and press Enter. (Don’t give up!)

    +
    +
    + + + + + +
    +
    Note
    +
    + You can only do this after flipping the card. +
    +
    +
    + + + + + +
    +
    Note
    +
    + If you answered no for a card, you will have an opportunity to see it again later in the same session. + FlashSpeed optimizes for cards you find difficult. +
    +
    +
    +
    + yes +
    +
    Figure 19. The next card (if any) will be immediately shown after answering.
    +
    +
    +


    +
    +
    +
    +

    4.4.3. Stopping a session: stop

    +
    +

    Format: stop

    +
    +
    +

    A play session will end automatically when there are no more cards to review. + However, you can always stop an ongoing session immediately by typing stop into the input box and pressing Enter. + But of course, don’t give up and try not to use this too often!

    +
    +
    + + + + + +
    +
    Note
    +
    + The session statistics will be shown either after completing a session or when manually stopping. +
    +
    +
    + + + + + +
    +
    Tip
    +
    + Other than clicking on the close button, you can also press Alt+F4 to close the statistics window. +
    +
    +
    +
    + stop +
    +
    Figure 20. Statistics shown after manually stopping the session.
    +
    +
    +


    +
    +
    +
    +
    +

    4.5. Save data

    +
    +

    All data in FlashSpeed (e.g. decks, cards) is saved automatically. It is all neatly placed in the folder you put FlashSpeed in.
    + You don’t have to save your precious flashcards manually!

    +
    +
    +


    +
    +
    +
    +
    +
    +

    5. FAQ

    +
    +
    +

    Q: Does this application require an Internet connection?
    + A: No, FlashSpeed does not require an Internet connection to use.

    +
    +
    +

    Q: What is the maximum length of text I can enter into a flashcard?
    + A: The flashcard method of studying benefits from succinct and concise flashcards. + Even though FlashSpeed does not limit the maximum length of text that can be entered and stored, + it will only show the text up to the size of the available display space. + Therefore, we recommend keeping any text under 60 characters.

    +
    +
    +

    Q: How do I save my data?
    + A: FlashSpeed automatically saves your data whenever you make a change. There is no need to save manually.

    +
    +
    +

    Q: Will my data be sent anywhere else or shared with third parties?
    + A: Your data is stored locally on your own computer. The application does not require any Internet connection so no data will be sent to any online servers.

    +
    +
    +

    Q: How do I transfer my data to another computer?
    + A: Simply copy the data folder in FlashSpeed’s home folder over to the home folder in the other computer.

    +
    +
    +

    Q: How do I update FlashSpeed to the latest version when there is an update?
    + A: You can check for any updates to FlashSpeed here. Then, you can follow the same instructions as found in Section 3, “Quick Start”.

    +
    +
    +

    Q: I am not able to run this application. What can I do?
    + A: Refer to Section 3, “Quick Start” for the installation guide. Ensure that your computer has Java 11 installed. FlashSpeed may not be able to run on other versions of Java.

    +
    +
    +


    +
    +
    +
    +
    +

    6. Command Summary

    +
    +
    +
      +
    • +

      Help : help

      +
    • +
    • +

      Exit : exit

      +
    • +
    • +

      Reset : reset

      +
    • +
    • +

      Select : select <index>
      + e.g. select 2

      +
    • +
    • +

      Create : create <deck>
      + e.g. create Japanese 1

      +
    • +
    • +

      Remove : remove <index>
      + e.g. remove 2

      +
    • +
    • +

      Rename : rename <index> <deck>
      + e.g. rename 2 Japanese Verbs

      +
    • +
    • +

      Play : play <index>
      + e.g. play 2

      +
    • +
    • +

      Add add <front>:<back>
      + e.g. add ありがとう:thanks

      +
    • +
    • +

      Edit : edit <index> <front>:<back> or edit <index> :<back> or edit <index> <front>:
      + e.g. edit 1 ありがとう:thanks or edit 1 :thanks or edit 1 ありがとう:

      +
    • +
    • +

      Delete : delete <index>
      + e.g. delete 3

      +
    • +
    • +

      Return : return

      +
    • +
    • +

      Flip : flip

      +
    • +
    • +

      Yes : yes

      +
    • +
    • +

      No : no

      +
    • +
    • +

      Stop : stop

      +
    • +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    View

    Command

    Library

    Deck

    Play

    help

    exit

    reset

    select

    create

    remove

    rename

    play

    add

    edit

    delete

    return

    flip

    yes

    no

    stop

    +
    +


    +
    +
    +
    +
    + + + diff --git a/src/main/resources/docs/images/AnswerNoSequenceDiagram.png b/src/main/resources/docs/images/AnswerNoSequenceDiagram.png new file mode 100644 index 00000000000..6891232bbd2 Binary files /dev/null and b/src/main/resources/docs/images/AnswerNoSequenceDiagram.png differ diff --git a/src/main/resources/docs/images/AnswerYesSequenceDiagram.png b/src/main/resources/docs/images/AnswerYesSequenceDiagram.png new file mode 100644 index 00000000000..8f3b09a81bc Binary files /dev/null and b/src/main/resources/docs/images/AnswerYesSequenceDiagram.png differ diff --git a/src/main/resources/docs/images/ArchitectureDiagram.png b/src/main/resources/docs/images/ArchitectureDiagram.png new file mode 100644 index 00000000000..aa2d337d932 Binary files /dev/null and b/src/main/resources/docs/images/ArchitectureDiagram.png differ diff --git a/src/main/resources/docs/images/ArchitectureSequenceDiagram.png b/src/main/resources/docs/images/ArchitectureSequenceDiagram.png new file mode 100644 index 00000000000..9223fb86e0e Binary files /dev/null and b/src/main/resources/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/src/main/resources/docs/images/BetterModelClassDiagram.png b/src/main/resources/docs/images/BetterModelClassDiagram.png new file mode 100644 index 00000000000..bc7ed18ae29 Binary files /dev/null and b/src/main/resources/docs/images/BetterModelClassDiagram.png differ diff --git a/src/main/resources/docs/images/CommitActivityDiagram.png b/src/main/resources/docs/images/CommitActivityDiagram.png new file mode 100644 index 00000000000..4de4fa4bf2b Binary files /dev/null and b/src/main/resources/docs/images/CommitActivityDiagram.png differ diff --git a/src/main/resources/docs/images/DeleteSequenceDiagram.png b/src/main/resources/docs/images/DeleteSequenceDiagram.png new file mode 100644 index 00000000000..fa327b39618 Binary files /dev/null and b/src/main/resources/docs/images/DeleteSequenceDiagram.png differ diff --git a/src/main/resources/docs/images/DependencyInjection.png b/src/main/resources/docs/images/DependencyInjection.png new file mode 100644 index 00000000000..be3a2f0b4fb Binary files /dev/null and b/src/main/resources/docs/images/DependencyInjection.png differ diff --git a/src/main/resources/docs/images/DependencyInjectionWithoutDIP.png b/src/main/resources/docs/images/DependencyInjectionWithoutDIP.png new file mode 100644 index 00000000000..0c34eb70b0b Binary files /dev/null and b/src/main/resources/docs/images/DependencyInjectionWithoutDIP.png differ diff --git a/src/main/resources/docs/images/EditSequenceDiagram.png b/src/main/resources/docs/images/EditSequenceDiagram.png new file mode 100644 index 00000000000..10129a0bf63 Binary files /dev/null and b/src/main/resources/docs/images/EditSequenceDiagram.png differ diff --git a/src/main/resources/docs/images/FlipSequenceDiagram.png b/src/main/resources/docs/images/FlipSequenceDiagram.png new file mode 100644 index 00000000000..797d3fe259b Binary files /dev/null and b/src/main/resources/docs/images/FlipSequenceDiagram.png differ diff --git a/src/main/resources/docs/images/LogicClassDiagram.png b/src/main/resources/docs/images/LogicClassDiagram.png new file mode 100644 index 00000000000..cb40bde3c80 Binary files /dev/null and b/src/main/resources/docs/images/LogicClassDiagram.png differ diff --git a/src/main/resources/docs/images/LogicStorageDIP.png b/src/main/resources/docs/images/LogicStorageDIP.png new file mode 100644 index 00000000000..871157f5a9c Binary files /dev/null and b/src/main/resources/docs/images/LogicStorageDIP.png differ diff --git a/src/main/resources/docs/images/ModelClassDiagram.png b/src/main/resources/docs/images/ModelClassDiagram.png new file mode 100644 index 00000000000..d22add82284 Binary files /dev/null and b/src/main/resources/docs/images/ModelClassDiagram.png differ diff --git a/src/main/resources/docs/images/PlaySequenceDiagram.png b/src/main/resources/docs/images/PlaySequenceDiagram.png new file mode 100644 index 00000000000..09e3a487b43 Binary files /dev/null and b/src/main/resources/docs/images/PlaySequenceDiagram.png differ diff --git a/src/main/resources/docs/images/PrintableInterface.png b/src/main/resources/docs/images/PrintableInterface.png new file mode 100644 index 00000000000..d7a69936be3 Binary files /dev/null and b/src/main/resources/docs/images/PrintableInterface.png differ diff --git a/src/main/resources/docs/images/ReadOnlyAddressBookUsage.png b/src/main/resources/docs/images/ReadOnlyAddressBookUsage.png new file mode 100644 index 00000000000..9ecbee64936 Binary files /dev/null and b/src/main/resources/docs/images/ReadOnlyAddressBookUsage.png differ diff --git a/docs/images/SeEduLogo.png b/src/main/resources/docs/images/SeEduLogo.png similarity index 100% rename from docs/images/SeEduLogo.png rename to src/main/resources/docs/images/SeEduLogo.png diff --git a/src/main/resources/docs/images/StorageClassDiagram.png b/src/main/resources/docs/images/StorageClassDiagram.png new file mode 100644 index 00000000000..d87c1216820 Binary files /dev/null and b/src/main/resources/docs/images/StorageClassDiagram.png differ diff --git a/src/main/resources/docs/images/Ui.png b/src/main/resources/docs/images/Ui.png new file mode 100644 index 00000000000..325706ffbab Binary files /dev/null and b/src/main/resources/docs/images/Ui.png differ diff --git a/src/main/resources/docs/images/Ui2.png b/src/main/resources/docs/images/Ui2.png new file mode 100644 index 00000000000..d8927f60b95 Binary files /dev/null and b/src/main/resources/docs/images/Ui2.png differ diff --git a/src/main/resources/docs/images/Ui3.png b/src/main/resources/docs/images/Ui3.png new file mode 100644 index 00000000000..9de8cba977a Binary files /dev/null and b/src/main/resources/docs/images/Ui3.png differ diff --git a/src/main/resources/docs/images/UiClassDiagram.png b/src/main/resources/docs/images/UiClassDiagram.png new file mode 100644 index 00000000000..8a9c56062f1 Binary files /dev/null and b/src/main/resources/docs/images/UiClassDiagram.png differ diff --git a/src/main/resources/docs/images/UndoRedoState0.png b/src/main/resources/docs/images/UndoRedoState0.png new file mode 100644 index 00000000000..8f7538cd884 Binary files /dev/null and b/src/main/resources/docs/images/UndoRedoState0.png differ diff --git a/src/main/resources/docs/images/UndoRedoState1.png b/src/main/resources/docs/images/UndoRedoState1.png new file mode 100644 index 00000000000..df9908d0948 Binary files /dev/null and b/src/main/resources/docs/images/UndoRedoState1.png differ diff --git a/src/main/resources/docs/images/UndoRedoState2.png b/src/main/resources/docs/images/UndoRedoState2.png new file mode 100644 index 00000000000..36519c1015b Binary files /dev/null and b/src/main/resources/docs/images/UndoRedoState2.png differ diff --git a/src/main/resources/docs/images/UndoRedoState3.png b/src/main/resources/docs/images/UndoRedoState3.png new file mode 100644 index 00000000000..19959d01712 Binary files /dev/null and b/src/main/resources/docs/images/UndoRedoState3.png differ diff --git a/src/main/resources/docs/images/UndoRedoState4.png b/src/main/resources/docs/images/UndoRedoState4.png new file mode 100644 index 00000000000..4c623e4f2c5 Binary files /dev/null and b/src/main/resources/docs/images/UndoRedoState4.png differ diff --git a/src/main/resources/docs/images/UndoRedoState5.png b/src/main/resources/docs/images/UndoRedoState5.png new file mode 100644 index 00000000000..84ad2afa6bd Binary files /dev/null and b/src/main/resources/docs/images/UndoRedoState5.png differ diff --git a/src/main/resources/docs/images/UndoSequenceDiagram.png b/src/main/resources/docs/images/UndoSequenceDiagram.png new file mode 100644 index 00000000000..6addcd3a8d9 Binary files /dev/null and b/src/main/resources/docs/images/UndoSequenceDiagram.png differ diff --git a/src/main/resources/docs/images/add-remark/$Remark.png b/src/main/resources/docs/images/add-remark/$Remark.png new file mode 100644 index 00000000000..959c634406d Binary files /dev/null and b/src/main/resources/docs/images/add-remark/$Remark.png differ diff --git a/src/main/resources/docs/images/add-remark/CommandInterface.png b/src/main/resources/docs/images/add-remark/CommandInterface.png new file mode 100644 index 00000000000..b52e7811c52 Binary files /dev/null and b/src/main/resources/docs/images/add-remark/CommandInterface.png differ diff --git a/src/main/resources/docs/images/add-remark/ContextMenu.png b/src/main/resources/docs/images/add-remark/ContextMenu.png new file mode 100644 index 00000000000..77536724e45 Binary files /dev/null and b/src/main/resources/docs/images/add-remark/ContextMenu.png differ diff --git a/src/main/resources/docs/images/add-remark/CreateTest.png b/src/main/resources/docs/images/add-remark/CreateTest.png new file mode 100644 index 00000000000..6b7d6dcafec Binary files /dev/null and b/src/main/resources/docs/images/add-remark/CreateTest.png differ diff --git a/src/main/resources/docs/images/add-remark/GradleRun.png b/src/main/resources/docs/images/add-remark/GradleRun.png new file mode 100644 index 00000000000..281cc45f098 Binary files /dev/null and b/src/main/resources/docs/images/add-remark/GradleRun.png differ diff --git a/src/main/resources/docs/images/add-remark/ParserInterface.png b/src/main/resources/docs/images/add-remark/ParserInterface.png new file mode 100644 index 00000000000..c02c9f7c0cb Binary files /dev/null and b/src/main/resources/docs/images/add-remark/ParserInterface.png differ diff --git a/src/main/resources/docs/images/add-remark/RemarkBound.png b/src/main/resources/docs/images/add-remark/RemarkBound.png new file mode 100644 index 00000000000..d335382b286 Binary files /dev/null and b/src/main/resources/docs/images/add-remark/RemarkBound.png differ diff --git a/src/main/resources/docs/images/add-remark/RemarkComplete.png b/src/main/resources/docs/images/add-remark/RemarkComplete.png new file mode 100644 index 00000000000..124ced2c752 Binary files /dev/null and b/src/main/resources/docs/images/add-remark/RemarkComplete.png differ diff --git a/src/main/resources/docs/images/add-remark/RemarkFailureOutput.png b/src/main/resources/docs/images/add-remark/RemarkFailureOutput.png new file mode 100644 index 00000000000..351257ea332 Binary files /dev/null and b/src/main/resources/docs/images/add-remark/RemarkFailureOutput.png differ diff --git a/src/main/resources/docs/images/add-remark/RemarkHello.png b/src/main/resources/docs/images/add-remark/RemarkHello.png new file mode 100644 index 00000000000..aad48d02f8f Binary files /dev/null and b/src/main/resources/docs/images/add-remark/RemarkHello.png differ diff --git a/src/main/resources/docs/images/add-remark/RemarkNotImplemented.png b/src/main/resources/docs/images/add-remark/RemarkNotImplemented.png new file mode 100644 index 00000000000..1d187f39403 Binary files /dev/null and b/src/main/resources/docs/images/add-remark/RemarkNotImplemented.png differ diff --git a/src/main/resources/docs/images/add.png b/src/main/resources/docs/images/add.png new file mode 100644 index 00000000000..e52d6f2ea32 Binary files /dev/null and b/src/main/resources/docs/images/add.png differ diff --git a/src/main/resources/docs/images/amrl.png b/src/main/resources/docs/images/amrl.png new file mode 100644 index 00000000000..cd8ac1e770b Binary files /dev/null and b/src/main/resources/docs/images/amrl.png differ diff --git a/src/main/resources/docs/images/appveyor/add-project-1.png b/src/main/resources/docs/images/appveyor/add-project-1.png new file mode 100644 index 00000000000..a244bce0146 Binary files /dev/null and b/src/main/resources/docs/images/appveyor/add-project-1.png differ diff --git a/src/main/resources/docs/images/appveyor/add-project-2.png b/src/main/resources/docs/images/appveyor/add-project-2.png new file mode 100644 index 00000000000..7578f8214a8 Binary files /dev/null and b/src/main/resources/docs/images/appveyor/add-project-2.png differ diff --git a/src/main/resources/docs/images/appveyor/add-project-3.png b/src/main/resources/docs/images/appveyor/add-project-3.png new file mode 100644 index 00000000000..c38f521621f Binary files /dev/null and b/src/main/resources/docs/images/appveyor/add-project-3.png differ diff --git a/src/main/resources/docs/images/appveyor/ci-log.png b/src/main/resources/docs/images/appveyor/ci-log.png new file mode 100644 index 00000000000..40e0f5a52d9 Binary files /dev/null and b/src/main/resources/docs/images/appveyor/ci-log.png differ diff --git a/src/main/resources/docs/images/appveyor/ci-pending.png b/src/main/resources/docs/images/appveyor/ci-pending.png new file mode 100644 index 00000000000..a7d2359994f Binary files /dev/null and b/src/main/resources/docs/images/appveyor/ci-pending.png differ diff --git a/src/main/resources/docs/images/appveyor/login.png b/src/main/resources/docs/images/appveyor/login.png new file mode 100644 index 00000000000..e5719a54604 Binary files /dev/null and b/src/main/resources/docs/images/appveyor/login.png differ diff --git a/src/main/resources/docs/images/appveyor/project-settings-1.png b/src/main/resources/docs/images/appveyor/project-settings-1.png new file mode 100644 index 00000000000..b772b3f6842 Binary files /dev/null and b/src/main/resources/docs/images/appveyor/project-settings-1.png differ diff --git a/src/main/resources/docs/images/appveyor/project-settings-2.png b/src/main/resources/docs/images/appveyor/project-settings-2.png new file mode 100644 index 00000000000..5ff7f15e46f Binary files /dev/null and b/src/main/resources/docs/images/appveyor/project-settings-2.png differ diff --git a/src/main/resources/docs/images/appveyor/project-settings-3.png b/src/main/resources/docs/images/appveyor/project-settings-3.png new file mode 100644 index 00000000000..30485db350d Binary files /dev/null and b/src/main/resources/docs/images/appveyor/project-settings-3.png differ diff --git a/src/main/resources/docs/images/build_pending.png b/src/main/resources/docs/images/build_pending.png new file mode 100644 index 00000000000..2e2f0d4380c Binary files /dev/null and b/src/main/resources/docs/images/build_pending.png differ diff --git a/src/main/resources/docs/images/checkstyle-idea-configuration.png b/src/main/resources/docs/images/checkstyle-idea-configuration.png new file mode 100644 index 00000000000..d279d3a4e97 Binary files /dev/null and b/src/main/resources/docs/images/checkstyle-idea-configuration.png differ diff --git a/src/main/resources/docs/images/checkstyle-idea-scan-scope.png b/src/main/resources/docs/images/checkstyle-idea-scan-scope.png new file mode 100644 index 00000000000..39a5b57b118 Binary files /dev/null and b/src/main/resources/docs/images/checkstyle-idea-scan-scope.png differ diff --git a/src/main/resources/docs/images/chrome_save_as_pdf.png b/src/main/resources/docs/images/chrome_save_as_pdf.png new file mode 100644 index 00000000000..53a1190bd48 Binary files /dev/null and b/src/main/resources/docs/images/chrome_save_as_pdf.png differ diff --git a/src/main/resources/docs/images/coveralls/badge_repo.png b/src/main/resources/docs/images/coveralls/badge_repo.png new file mode 100644 index 00000000000..e653ffd0782 Binary files /dev/null and b/src/main/resources/docs/images/coveralls/badge_repo.png differ diff --git a/src/main/resources/docs/images/coveralls/coverage_asciidoc_code.png b/src/main/resources/docs/images/coveralls/coverage_asciidoc_code.png new file mode 100644 index 00000000000..fc6d925d838 Binary files /dev/null and b/src/main/resources/docs/images/coveralls/coverage_asciidoc_code.png differ diff --git a/src/main/resources/docs/images/coveralls/coverage_report.png b/src/main/resources/docs/images/coveralls/coverage_report.png new file mode 100644 index 00000000000..8fdfc7da1bd Binary files /dev/null and b/src/main/resources/docs/images/coveralls/coverage_report.png differ diff --git a/src/main/resources/docs/images/coveralls/disable_comments.png b/src/main/resources/docs/images/coveralls/disable_comments.png new file mode 100644 index 00000000000..2023de102b6 Binary files /dev/null and b/src/main/resources/docs/images/coveralls/disable_comments.png differ diff --git a/src/main/resources/docs/images/coveralls/flick_repository_switch.png b/src/main/resources/docs/images/coveralls/flick_repository_switch.png new file mode 100644 index 00000000000..5900dc88387 Binary files /dev/null and b/src/main/resources/docs/images/coveralls/flick_repository_switch.png differ diff --git a/src/main/resources/docs/images/coveralls/github_settings.png b/src/main/resources/docs/images/coveralls/github_settings.png new file mode 100644 index 00000000000..728fe2bfaea Binary files /dev/null and b/src/main/resources/docs/images/coveralls/github_settings.png differ diff --git a/src/main/resources/docs/images/coveralls/sync_repos.png b/src/main/resources/docs/images/coveralls/sync_repos.png new file mode 100644 index 00000000000..b3c910a8be6 Binary files /dev/null and b/src/main/resources/docs/images/coveralls/sync_repos.png differ diff --git a/src/main/resources/docs/images/create.png b/src/main/resources/docs/images/create.png new file mode 100644 index 00000000000..5a567fbe14f Binary files /dev/null and b/src/main/resources/docs/images/create.png differ diff --git a/docs/images/damithc.jpg b/src/main/resources/docs/images/damithc.jpg similarity index 100% rename from docs/images/damithc.jpg rename to src/main/resources/docs/images/damithc.jpg diff --git a/src/main/resources/docs/images/delete.png b/src/main/resources/docs/images/delete.png new file mode 100644 index 00000000000..ac1e5988622 Binary files /dev/null and b/src/main/resources/docs/images/delete.png differ diff --git a/src/main/resources/docs/images/edit.png b/src/main/resources/docs/images/edit.png new file mode 100644 index 00000000000..c54d2c0b2c5 Binary files /dev/null and b/src/main/resources/docs/images/edit.png differ diff --git a/src/main/resources/docs/images/flick_repository_switch.png b/src/main/resources/docs/images/flick_repository_switch.png new file mode 100644 index 00000000000..a6009dd44cd Binary files /dev/null and b/src/main/resources/docs/images/flick_repository_switch.png differ diff --git a/src/main/resources/docs/images/flip.png b/src/main/resources/docs/images/flip.png new file mode 100644 index 00000000000..eceb7fb5c53 Binary files /dev/null and b/src/main/resources/docs/images/flip.png differ diff --git a/src/main/resources/docs/images/generate_token.png b/src/main/resources/docs/images/generate_token.png new file mode 100644 index 00000000000..aa8cee9f3be Binary files /dev/null and b/src/main/resources/docs/images/generate_token.png differ diff --git a/src/main/resources/docs/images/github_repo_settings.png b/src/main/resources/docs/images/github_repo_settings.png new file mode 100644 index 00000000000..101d6e9d562 Binary files /dev/null and b/src/main/resources/docs/images/github_repo_settings.png differ diff --git a/src/main/resources/docs/images/grant_access.png b/src/main/resources/docs/images/grant_access.png new file mode 100644 index 00000000000..beb4c0ddc8b Binary files /dev/null and b/src/main/resources/docs/images/grant_access.png differ diff --git a/src/main/resources/docs/images/help.png b/src/main/resources/docs/images/help.png new file mode 100644 index 00000000000..da9a0f4a78b Binary files /dev/null and b/src/main/resources/docs/images/help.png differ diff --git a/src/main/resources/docs/images/kschiew.png b/src/main/resources/docs/images/kschiew.png new file mode 100644 index 00000000000..81be18531eb Binary files /dev/null and b/src/main/resources/docs/images/kschiew.png differ diff --git a/src/main/resources/docs/images/lacedaemon98.png b/src/main/resources/docs/images/lacedaemon98.png new file mode 100644 index 00000000000..b40d0d42e31 Binary files /dev/null and b/src/main/resources/docs/images/lacedaemon98.png differ diff --git a/docs/images/lejolly.jpg b/src/main/resources/docs/images/lejolly.jpg similarity index 100% rename from docs/images/lejolly.jpg rename to src/main/resources/docs/images/lejolly.jpg diff --git a/src/main/resources/docs/images/library.png b/src/main/resources/docs/images/library.png new file mode 100644 index 00000000000..8b105584486 Binary files /dev/null and b/src/main/resources/docs/images/library.png differ diff --git a/docs/images/m133225.jpg b/src/main/resources/docs/images/m133225.jpg similarity index 100% rename from docs/images/m133225.jpg rename to src/main/resources/docs/images/m133225.jpg diff --git a/src/main/resources/docs/images/ncslzh.png b/src/main/resources/docs/images/ncslzh.png new file mode 100644 index 00000000000..0d601f1cd7f Binary files /dev/null and b/src/main/resources/docs/images/ncslzh.png differ diff --git a/src/main/resources/docs/images/netlify/change_site_name.png b/src/main/resources/docs/images/netlify/change_site_name.png new file mode 100644 index 00000000000..989c7caa32b Binary files /dev/null and b/src/main/resources/docs/images/netlify/change_site_name.png differ diff --git a/src/main/resources/docs/images/netlify/grant_or_request_access.png b/src/main/resources/docs/images/netlify/grant_or_request_access.png new file mode 100644 index 00000000000..45ce7cb4305 Binary files /dev/null and b/src/main/resources/docs/images/netlify/grant_or_request_access.png differ diff --git a/src/main/resources/docs/images/netlify/netlify_details.png b/src/main/resources/docs/images/netlify/netlify_details.png new file mode 100644 index 00000000000..74983ffae39 Binary files /dev/null and b/src/main/resources/docs/images/netlify/netlify_details.png differ diff --git a/src/main/resources/docs/images/netlify/temp_site_name.png b/src/main/resources/docs/images/netlify/temp_site_name.png new file mode 100644 index 00000000000..72aca039392 Binary files /dev/null and b/src/main/resources/docs/images/netlify/temp_site_name.png differ diff --git a/src/main/resources/docs/images/olixino.png b/src/main/resources/docs/images/olixino.png new file mode 100644 index 00000000000..0aafa2e939b Binary files /dev/null and b/src/main/resources/docs/images/olixino.png differ diff --git a/src/main/resources/docs/images/plantuml/ABeforeC.png b/src/main/resources/docs/images/plantuml/ABeforeC.png new file mode 100644 index 00000000000..39f28934ad5 Binary files /dev/null and b/src/main/resources/docs/images/plantuml/ABeforeC.png differ diff --git a/src/main/resources/docs/images/plantuml/AllDown.png b/src/main/resources/docs/images/plantuml/AllDown.png new file mode 100644 index 00000000000..a922ed70173 Binary files /dev/null and b/src/main/resources/docs/images/plantuml/AllDown.png differ diff --git a/src/main/resources/docs/images/plantuml/ArrowLength.png b/src/main/resources/docs/images/plantuml/ArrowLength.png new file mode 100644 index 00000000000..01befd58ee5 Binary files /dev/null and b/src/main/resources/docs/images/plantuml/ArrowLength.png differ diff --git a/src/main/resources/docs/images/plantuml/CBeforeA.png b/src/main/resources/docs/images/plantuml/CBeforeA.png new file mode 100644 index 00000000000..b4157db03e8 Binary files /dev/null and b/src/main/resources/docs/images/plantuml/CBeforeA.png differ diff --git a/src/main/resources/docs/images/plantuml/ConfiguringGraphviz.png b/src/main/resources/docs/images/plantuml/ConfiguringGraphviz.png new file mode 100644 index 00000000000..19a9cdb23d6 Binary files /dev/null and b/src/main/resources/docs/images/plantuml/ConfiguringGraphviz.png differ diff --git a/src/main/resources/docs/images/plantuml/EditingDeleteSequenceDiagram.png b/src/main/resources/docs/images/plantuml/EditingDeleteSequenceDiagram.png new file mode 100644 index 00000000000..88e90d4269b Binary files /dev/null and b/src/main/resources/docs/images/plantuml/EditingDeleteSequenceDiagram.png differ diff --git a/src/main/resources/docs/images/plantuml/HiddenArrows.png b/src/main/resources/docs/images/plantuml/HiddenArrows.png new file mode 100644 index 00000000000..43c138a8bbf Binary files /dev/null and b/src/main/resources/docs/images/plantuml/HiddenArrows.png differ diff --git a/src/main/resources/docs/images/plantuml/PackagesAndConsistency.png b/src/main/resources/docs/images/plantuml/PackagesAndConsistency.png new file mode 100644 index 00000000000..28b9787d170 Binary files /dev/null and b/src/main/resources/docs/images/plantuml/PackagesAndConsistency.png differ diff --git a/src/main/resources/docs/images/plantuml/RawUiDiagram.png b/src/main/resources/docs/images/plantuml/RawUiDiagram.png new file mode 100644 index 00000000000..b7f3cbe55e3 Binary files /dev/null and b/src/main/resources/docs/images/plantuml/RawUiDiagram.png differ diff --git a/src/main/resources/docs/images/plantuml/UpAndDown.png b/src/main/resources/docs/images/plantuml/UpAndDown.png new file mode 100644 index 00000000000..b60d53a0258 Binary files /dev/null and b/src/main/resources/docs/images/plantuml/UpAndDown.png differ diff --git a/src/main/resources/docs/images/remove.png b/src/main/resources/docs/images/remove.png new file mode 100644 index 00000000000..937f74757b2 Binary files /dev/null and b/src/main/resources/docs/images/remove.png differ diff --git a/src/main/resources/docs/images/remove/$address.png b/src/main/resources/docs/images/remove/$address.png new file mode 100644 index 00000000000..cf0434e0e83 Binary files /dev/null and b/src/main/resources/docs/images/remove/$address.png differ diff --git a/src/main/resources/docs/images/remove/SafeDeleteConflicts.png b/src/main/resources/docs/images/remove/SafeDeleteConflicts.png new file mode 100644 index 00000000000..8f0abeffd4d Binary files /dev/null and b/src/main/resources/docs/images/remove/SafeDeleteConflicts.png differ diff --git a/src/main/resources/docs/images/remove/UnsafeDelete.png b/src/main/resources/docs/images/remove/UnsafeDelete.png new file mode 100644 index 00000000000..9e376d02a0c Binary files /dev/null and b/src/main/resources/docs/images/remove/UnsafeDelete.png differ diff --git a/src/main/resources/docs/images/remove/UnsafeDeleteOnField.png b/src/main/resources/docs/images/remove/UnsafeDeleteOnField.png new file mode 100644 index 00000000000..44d5bb0a442 Binary files /dev/null and b/src/main/resources/docs/images/remove/UnsafeDeleteOnField.png differ diff --git a/src/main/resources/docs/images/rename.png b/src/main/resources/docs/images/rename.png new file mode 100644 index 00000000000..ea9cea55023 Binary files /dev/null and b/src/main/resources/docs/images/rename.png differ diff --git a/src/main/resources/docs/images/request_access.png b/src/main/resources/docs/images/request_access.png new file mode 100644 index 00000000000..12e8a81bd28 Binary files /dev/null and b/src/main/resources/docs/images/request_access.png differ diff --git a/src/main/resources/docs/images/reset.png b/src/main/resources/docs/images/reset.png new file mode 100644 index 00000000000..c17ebd6c688 Binary files /dev/null and b/src/main/resources/docs/images/reset.png differ diff --git a/src/main/resources/docs/images/review_and_add.png b/src/main/resources/docs/images/review_and_add.png new file mode 100644 index 00000000000..81d60a36e88 Binary files /dev/null and b/src/main/resources/docs/images/review_and_add.png differ diff --git a/src/main/resources/docs/images/signing_in.png b/src/main/resources/docs/images/signing_in.png new file mode 100644 index 00000000000..6d1ad4fe26f Binary files /dev/null and b/src/main/resources/docs/images/signing_in.png differ diff --git a/src/main/resources/docs/images/testfx-idea-accessibility-permissions.png b/src/main/resources/docs/images/testfx-idea-accessibility-permissions.png new file mode 100644 index 00000000000..02b3097114f Binary files /dev/null and b/src/main/resources/docs/images/testfx-idea-accessibility-permissions.png differ diff --git a/src/main/resources/docs/images/tracing/DebuggerStep1.png b/src/main/resources/docs/images/tracing/DebuggerStep1.png new file mode 100644 index 00000000000..6d088ae63de Binary files /dev/null and b/src/main/resources/docs/images/tracing/DebuggerStep1.png differ diff --git a/src/main/resources/docs/images/tracing/EditCommand.png b/src/main/resources/docs/images/tracing/EditCommand.png new file mode 100644 index 00000000000..ed34ad08b98 Binary files /dev/null and b/src/main/resources/docs/images/tracing/EditCommand.png differ diff --git a/src/main/resources/docs/images/tracing/Execute.png b/src/main/resources/docs/images/tracing/Execute.png new file mode 100644 index 00000000000..66b7044e207 Binary files /dev/null and b/src/main/resources/docs/images/tracing/Execute.png differ diff --git a/src/main/resources/docs/images/tracing/FindUsages.png b/src/main/resources/docs/images/tracing/FindUsages.png new file mode 100644 index 00000000000..16f94a53d09 Binary files /dev/null and b/src/main/resources/docs/images/tracing/FindUsages.png differ diff --git a/src/main/resources/docs/images/tracing/LeftGutter.png b/src/main/resources/docs/images/tracing/LeftGutter.png new file mode 100644 index 00000000000..571acf99e7b Binary files /dev/null and b/src/main/resources/docs/images/tracing/LeftGutter.png differ diff --git a/src/main/resources/docs/images/tracing/LogicSequenceDiagram.png b/src/main/resources/docs/images/tracing/LogicSequenceDiagram.png new file mode 100644 index 00000000000..c9b1f6cc232 Binary files /dev/null and b/src/main/resources/docs/images/tracing/LogicSequenceDiagram.png differ diff --git a/src/main/resources/docs/images/tracing/ShowExecutionPoint.png b/src/main/resources/docs/images/tracing/ShowExecutionPoint.png new file mode 100644 index 00000000000..ea72176fa64 Binary files /dev/null and b/src/main/resources/docs/images/tracing/ShowExecutionPoint.png differ diff --git a/src/main/resources/docs/images/tracing/StepInto.png b/src/main/resources/docs/images/tracing/StepInto.png new file mode 100644 index 00000000000..ddfa0e2aeb8 Binary files /dev/null and b/src/main/resources/docs/images/tracing/StepInto.png differ diff --git a/src/main/resources/docs/images/tracing/StepOver.png b/src/main/resources/docs/images/tracing/StepOver.png new file mode 100644 index 00000000000..ed5fb276e29 Binary files /dev/null and b/src/main/resources/docs/images/tracing/StepOver.png differ diff --git a/src/main/resources/docs/images/tracing/StructureToolWindow.png b/src/main/resources/docs/images/tracing/StructureToolWindow.png new file mode 100644 index 00000000000..c377c331d5f Binary files /dev/null and b/src/main/resources/docs/images/tracing/StructureToolWindow.png differ diff --git a/src/main/resources/docs/images/tracing/Variables.png b/src/main/resources/docs/images/tracing/Variables.png new file mode 100644 index 00000000000..02ea7b15520 Binary files /dev/null and b/src/main/resources/docs/images/tracing/Variables.png differ diff --git a/src/main/resources/docs/images/travis_add_token.png b/src/main/resources/docs/images/travis_add_token.png new file mode 100644 index 00000000000..06e4dd075fa Binary files /dev/null and b/src/main/resources/docs/images/travis_add_token.png differ diff --git a/src/main/resources/docs/images/travis_build.png b/src/main/resources/docs/images/travis_build.png new file mode 100644 index 00000000000..0c4061bc0e2 Binary files /dev/null and b/src/main/resources/docs/images/travis_build.png differ diff --git a/src/main/resources/docs/images/yes.png b/src/main/resources/docs/images/yes.png new file mode 100644 index 00000000000..85e9a4f4bbc Binary files /dev/null and b/src/main/resources/docs/images/yes.png differ diff --git a/docs/images/yijinl.jpg b/src/main/resources/docs/images/yijinl.jpg similarity index 100% rename from docs/images/yijinl.jpg rename to src/main/resources/docs/images/yijinl.jpg diff --git a/docs/images/yl_coder.jpg b/src/main/resources/docs/images/yl_coder.jpg similarity index 100% rename from docs/images/yl_coder.jpg rename to src/main/resources/docs/images/yl_coder.jpg diff --git a/src/main/resources/docs/stylesheets/asciidoctor.css b/src/main/resources/docs/stylesheets/asciidoctor.css new file mode 100644 index 00000000000..36590bf346c --- /dev/null +++ b/src/main/resources/docs/stylesheets/asciidoctor.css @@ -0,0 +1,407 @@ +/* Asciidoctor default stylesheet | MIT License | http://asciidoctor.org */ +/* Remove comment around @import statement below when using as a custom stylesheet */ +/*@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700";*/ +article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block} +audio,canvas,video{display:inline-block} +audio:not([controls]){display:none;height:0} +[hidden],template{display:none} +script{display:none!important} +html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%} +body{margin:0} +a{background:transparent} +a:focus{outline:thin dotted} +a:active,a:hover{outline:0} +h1{font-size:2em;margin:.67em 0} +abbr[title]{border-bottom:1px dotted} +b,strong{font-weight:bold} +dfn{font-style:italic} +hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0} +mark{background:#ff0;color:#000} +code,kbd,pre,samp{font-family:monospace;font-size:1em} +pre{white-space:pre-wrap} +q{quotes:"\201C" "\201D" "\2018" "\2019"} +small{font-size:80%} +sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} +sup{top:-.5em} +sub{bottom:-.25em} +img{border:0} +svg:not(:root){overflow:hidden} +figure{margin:0} +fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em} +legend{border:0;padding:0} +button,input,select,textarea{font-family:inherit;font-size:100%;margin:0} +button,input{line-height:normal} +button,select{text-transform:none} +button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer} +button[disabled],html input[disabled]{cursor:default} +input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0} +input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box} +input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0} +textarea{overflow:auto;vertical-align:top} +table{border-collapse:collapse;border-spacing:0} +*,*:before,*:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box} +html,body{font-size:100%} +body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto} +a:hover{cursor:pointer} +img,object,embed{max-width:100%;height:auto} +object,embed{height:100%} +img{-ms-interpolation-mode:bicubic} +.left{float:left!important} +.right{float:right!important} +.text-left{text-align:left!important} +.text-right{text-align:right!important} +.text-center{text-align:center!important} +.text-justify{text-align:justify!important} +.hide{display:none} +body{-webkit-font-smoothing:antialiased} +img,object,svg{display:inline-block;vertical-align:middle} +textarea{height:auto;min-height:50px} +select{width:100%} +.center{margin-left:auto;margin-right:auto} +.spread{width:100%} +p.lead,.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{font-size:1.21875em;line-height:1.6} +.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em} +div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr} +a{color:#2156a5;text-decoration:underline;line-height:inherit} +a:hover,a:focus{color:#1d4b8f} +a img{border:none} +p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility} +p aside{font-size:.875em;line-height:1.35;font-style:italic} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em} +h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0} +h1{font-size:2.125em} +h2{font-size:1.6875em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em} +h4,h5{font-size:1.125em} +h6{font-size:1em} +hr{border:solid #ddddd8;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0} +em,i{font-style:italic;line-height:inherit} +strong,b{font-weight:bold;line-height:inherit} +small{font-size:60%;line-height:inherit} +code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)} +ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit} +ul,ol,ul.no-bullet,ol.no-bullet{margin-left:1.5em} +ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em} +ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit} +ul.square{list-style-type:square} +ul.circle{list-style-type:circle} +ul.disc{list-style-type:disc} +ul.no-bullet{list-style:none} +ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0} +dl dt{margin-bottom:.3125em;font-weight:bold} +dl dd{margin-bottom:1.25em} +abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help} +abbr{text-transform:none} +blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd} +blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)} +blockquote cite:before{content:"\2014 \0020"} +blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)} +blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)} +@media only screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2} +h1{font-size:2.75em} +h2{font-size:2.3125em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em} +h4{font-size:1.4375em}} +table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede} +table thead,table tfoot{background:#f7f8f7;font-weight:bold} +table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left} +table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)} +table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7} +table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6} +body{tab-size:4} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em} +h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400} +.clearfix:before,.clearfix:after,.float-group:before,.float-group:after{content:" ";display:table} +.clearfix:after,.float-group:after{clear:both} +*:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed} +pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed} +.keyseq{color:rgba(51,51,51,.8)} +kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap} +.keyseq kbd:first-child{margin-left:0} +.keyseq kbd:last-child{margin-right:0} +.menuseq,.menu{color:rgba(0,0,0,.8)} +b.button:before,b.button:after{position:relative;top:-1px;font-weight:400} +b.button:before{content:"[";padding:0 3px 0 2px} +b.button:after{content:"]";padding:0 2px 0 3px} +p a>code:hover{color:rgba(0,0,0,.9)} +#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em} +#header:before,#header:after,#content:before,#content:after,#footnotes:before,#footnotes:after,#footer:before,#footer:after{content:" ";display:table} +#header:after,#content:after,#footnotes:after,#footer:after{clear:both} +#content{margin-top:1.25em} +#content:before{content:none} +#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0} +#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #ddddd8} +#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #ddddd8;padding-bottom:8px} +#header .details{border-bottom:1px solid #ddddd8;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap} +#header .details span:first-child{margin-left:-.125em} +#header .details span.email a{color:rgba(0,0,0,.85)} +#header .details br{display:none} +#header .details br+span:before{content:"\00a0\2013\00a0"} +#header .details br+span.author:before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)} +#header .details br+span#revremark:before{content:"\00a0|\00a0"} +#header #revnumber{text-transform:capitalize} +#header #revnumber:after{content:"\00a0"} +#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #ddddd8;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem} +#toc{border-bottom:1px solid #efefed;padding-bottom:.5em} +#toc>ul{margin-left:.125em} +#toc ul.sectlevel0>li>a{font-style:italic} +#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0} +#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none} +#toc li{line-height:1.3334;margin-top:.3334em} +#toc a{text-decoration:none} +#toc a:active{text-decoration:underline} +#toctitle{color:#7a2518;font-size:1.2em} +@media only screen and (min-width:768px){#toctitle{font-size:1.375em} +body.toc2{padding-left:15em;padding-right:0} +#toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #efefed;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto} +#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em} +#toc.toc2>ul{font-size:.9em;margin-bottom:0} +#toc.toc2 ul ul{margin-left:0;padding-left:1em} +#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em} +body.toc2.toc-right{padding-left:0;padding-right:15em} +body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #efefed;left:auto;right:0}} +@media only screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0} +#toc.toc2{width:20em} +#toc.toc2 #toctitle{font-size:1.375em} +#toc.toc2>ul{font-size:.95em} +#toc.toc2 ul ul{padding-left:1.25em} +body.toc2.toc-right{padding-left:0;padding-right:20em}} +#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px} +#content #toc>:first-child{margin-top:0} +#content #toc>:last-child{margin-bottom:0} +#footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em} +#footer-text{color:rgba(255,255,255,.8);line-height:1.44} +.sect1{padding-bottom:.625em} +@media only screen and (min-width:768px){.sect1{padding-bottom:1.25em}} +.sect1+.sect1{border-top:1px solid #efefed} +#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400} +#content h1>a.anchor:before,h2>a.anchor:before,h3>a.anchor:before,#toctitle>a.anchor:before,.sidebarblock>.content>.title>a.anchor:before,h4>a.anchor:before,h5>a.anchor:before,h6>a.anchor:before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em} +#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible} +#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none} +#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221} +.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em} +.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic} +table.tableblock>caption.title{white-space:nowrap;overflow:visible;max-width:0} +.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{color:rgba(0,0,0,.85)} +table.tableblock #preamble>.sectionbody>.paragraph:first-of-type p{font-size:inherit} +.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%} +.admonitionblock>table td.icon{text-align:center;width:80px} +.admonitionblock>table td.icon img{max-width:none} +.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase} +.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #ddddd8;color:rgba(0,0,0,.6)} +.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0} +.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px} +.exampleblock>.content>:first-child{margin-top:0} +.exampleblock>.content>:last-child{margin-bottom:0} +.sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px} +.sidebarblock>:first-child{margin-top:0} +.sidebarblock>:last-child{margin-bottom:0} +.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center} +.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0} +.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8} +.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1} +.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;padding:1em;font-size:.8125em} +.literalblock pre.nowrap,.literalblock pre[class].nowrap,.listingblock pre.nowrap,.listingblock pre[class].nowrap{overflow-x:auto;white-space:pre;word-wrap:normal} +@media only screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}} +@media only screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}} +.literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)} +.listingblock pre.highlightjs{padding:0} +.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px} +.listingblock pre.prettyprint{border-width:0} +.listingblock>.content{position:relative} +.listingblock code[data-lang]:before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999} +.listingblock:hover code[data-lang]:before{display:block} +.listingblock.terminal pre .command:before{content:attr(data-prompt);padding-right:.5em;color:#999} +.listingblock.terminal pre .command:not([data-prompt]):before{content:"$"} +table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:none} +table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0;line-height:1.45} +table.pyhltable td.code{padding-left:.75em;padding-right:0} +pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #ddddd8} +pre.pygments .lineno{display:inline-block;margin-right:.25em} +table.pyhltable .linenodiv{background:none!important;padding-right:0!important} +.quoteblock{margin:0 1em 1.25em 1.5em;display:table} +.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em} +.quoteblock blockquote,.quoteblock blockquote p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify} +.quoteblock blockquote{margin:0;padding:0;border:0} +.quoteblock blockquote:before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)} +.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0} +.quoteblock .attribution{margin-top:.5em;margin-right:.5ex;text-align:right} +.quoteblock .quoteblock{margin-left:0;margin-right:0;padding:.5em 0;border-left:3px solid rgba(0,0,0,.6)} +.quoteblock .quoteblock blockquote{padding:0 0 0 .75em} +.quoteblock .quoteblock blockquote:before{display:none} +.verseblock{margin:0 1em 1.25em 1em} +.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility} +.verseblock pre strong{font-weight:400} +.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex} +.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic} +.quoteblock .attribution br,.verseblock .attribution br{display:none} +.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)} +.quoteblock.abstract{margin:0 0 1.25em 0;display:block} +.quoteblock.abstract blockquote,.quoteblock.abstract blockquote p{text-align:left;word-spacing:0} +.quoteblock.abstract blockquote:before,.quoteblock.abstract blockquote p:first-of-type:before{display:none} +table.tableblock{max-width:100%;border-collapse:separate} +table.tableblock td>.paragraph:last-child p>p:last-child,table.tableblock th>p:last-child,table.tableblock td>p:last-child{margin-bottom:0} +table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede} +table.grid-all th.tableblock,table.grid-all td.tableblock{border-width:0 1px 1px 0} +table.grid-all tfoot>tr>th.tableblock,table.grid-all tfoot>tr>td.tableblock{border-width:1px 1px 0 0} +table.grid-cols th.tableblock,table.grid-cols td.tableblock{border-width:0 1px 0 0} +table.grid-all *>tr>.tableblock:last-child,table.grid-cols *>tr>.tableblock:last-child{border-right-width:0} +table.grid-rows th.tableblock,table.grid-rows td.tableblock{border-width:0 0 1px 0} +table.grid-all tbody>tr:last-child>th.tableblock,table.grid-all tbody>tr:last-child>td.tableblock,table.grid-all thead:last-child>tr>th.tableblock,table.grid-rows tbody>tr:last-child>th.tableblock,table.grid-rows tbody>tr:last-child>td.tableblock,table.grid-rows thead:last-child>tr>th.tableblock{border-bottom-width:0} +table.grid-rows tfoot>tr>th.tableblock,table.grid-rows tfoot>tr>td.tableblock{border-width:1px 0 0 0} +table.frame-all{border-width:1px} +table.frame-sides{border-width:0 1px} +table.frame-topbot{border-width:1px 0} +th.halign-left,td.halign-left{text-align:left} +th.halign-right,td.halign-right{text-align:right} +th.halign-center,td.halign-center{text-align:center} +th.valign-top,td.valign-top{vertical-align:top} +th.valign-bottom,td.valign-bottom{vertical-align:bottom} +th.valign-middle,td.valign-middle{vertical-align:middle} +table thead th,table tfoot th{font-weight:bold} +tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7} +tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold} +p.tableblock>code:only-child{background:none;padding:0} +p.tableblock{font-size:1em} +td>div.verse{white-space:pre} +ol{margin-left:1.75em} +ul li ol{margin-left:1.5em} +dl dd{margin-left:1.125em} +dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0} +ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em} +ul.unstyled,ol.unnumbered,ul.checklist,ul.none{list-style-type:none} +ul.unstyled,ol.unnumbered,ul.checklist{margin-left:.625em} +ul.checklist li>p:first-child>.fa-square-o:first-child,ul.checklist li>p:first-child>.fa-check-square-o:first-child{width:1em;font-size:.85em} +ul.checklist li>p:first-child>input[type="checkbox"]:first-child{width:1em;position:relative;top:1px} +ul.inline{margin:0 auto .625em auto;margin-left:-1.375em;margin-right:0;padding:0;list-style:none;overflow:hidden} +ul.inline>li{list-style:none;float:left;margin-left:1.375em;display:block} +ul.inline>li>*{display:block} +.unstyled dl dt{font-weight:400;font-style:normal} +ol.arabic{list-style-type:decimal} +ol.decimal{list-style-type:decimal-leading-zero} +ol.loweralpha{list-style-type:lower-alpha} +ol.upperalpha{list-style-type:upper-alpha} +ol.lowerroman{list-style-type:lower-roman} +ol.upperroman{list-style-type:upper-roman} +ol.lowergreek{list-style-type:lower-greek} +.hdlist>table,.colist>table{border:0;background:none} +.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none} +td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em} +td.hdlist1{font-weight:bold;padding-bottom:1.25em} +.literalblock+.colist,.listingblock+.colist{margin-top:-.5em} +.colist>table tr>td:first-of-type{padding:0 .75em;line-height:1} +.colist>table tr>td:last-of-type{padding:.25em 0} +.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd} +.imageblock.left,.imageblock[style*="float: left"]{margin:.25em .625em 1.25em 0} +.imageblock.right,.imageblock[style*="float: right"]{margin:.25em 0 1.25em .625em} +.imageblock>.title{margin-bottom:0} +.imageblock.thumb,.imageblock.th{border-width:6px} +.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em} +.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0} +.image.left{margin-right:.625em} +.image.right{margin-left:.625em} +a.image{text-decoration:none;display:inline-block} +a.image object{pointer-events:none} +sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super} +sup.footnote a,sup.footnoteref a{text-decoration:none} +sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline} +#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em} +#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em 0;border-width:1px 0 0 0} +#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;text-indent:-1.05em;margin-bottom:.2em} +#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none} +#footnotes .footnote:last-of-type{margin-bottom:0} +#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0} +.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0} +.gist .file-data>table td.line-data{width:99%} +div.unbreakable{page-break-inside:avoid} +.big{font-size:larger} +.small{font-size:smaller} +.underline{text-decoration:underline} +.overline{text-decoration:overline} +.line-through{text-decoration:line-through} +.aqua{color:#00bfbf} +.aqua-background{background-color:#00fafa} +.black{color:#000} +.black-background{background-color:#000} +.blue{color:#0000bf} +.blue-background{background-color:#0000fa} +.fuchsia{color:#bf00bf} +.fuchsia-background{background-color:#fa00fa} +.gray{color:#606060} +.gray-background{background-color:#7d7d7d} +.green{color:#006000} +.green-background{background-color:#007d00} +.lime{color:#00bf00} +.lime-background{background-color:#00fa00} +.maroon{color:#600000} +.maroon-background{background-color:#7d0000} +.navy{color:#000060} +.navy-background{background-color:#00007d} +.olive{color:#606000} +.olive-background{background-color:#7d7d00} +.purple{color:#600060} +.purple-background{background-color:#7d007d} +.red{color:#bf0000} +.red-background{background-color:#fa0000} +.silver{color:#909090} +.silver-background{background-color:#bcbcbc} +.teal{color:#006060} +.teal-background{background-color:#007d7d} +.white{color:#bfbfbf} +.white-background{background-color:#fafafa} +.yellow{color:#bfbf00} +.yellow-background{background-color:#fafa00} +span.icon>.fa{cursor:default} +.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default} +.admonitionblock td.icon .icon-note:before{content:"\f05a";color:#19407c} +.admonitionblock td.icon .icon-tip:before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111} +.admonitionblock td.icon .icon-warning:before{content:"\f071";color:#bf6900} +.admonitionblock td.icon .icon-caution:before{content:"\f06d";color:#bf3400} +.admonitionblock td.icon .icon-important:before{content:"\f06a";color:#bf0000} +.conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold} +.conum[data-value] *{color:#fff!important} +.conum[data-value]+b{display:none} +.conum[data-value]:after{content:attr(data-value)} +pre .conum[data-value]{position:relative;top:-.125em} +b.conum *{color:inherit!important} +.conum:not([data-value]):empty{display:none} +dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility} +h1,h2,p,td.content,span.alt{letter-spacing:-.01em} +p strong,td.content strong,div.footnote strong{letter-spacing:-.005em} +p,blockquote,dt,td.content,span.alt{font-size:1.0625rem} +p{margin-bottom:1.25rem} +.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em} +.exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc} +.print-only{display:none!important} +@media print{@page{margin:1.25cm .75cm} +*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important} +a{color:inherit!important;text-decoration:underline!important} +a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important} +a[href^="http:"]:not(.bare):after,a[href^="https:"]:not(.bare):after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em} +abbr[title]:after{content:" (" attr(title) ")"} +pre,blockquote,tr,img,object,svg{page-break-inside:avoid} +thead{display:table-header-group} +svg{max-width:100%} +p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3} +h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid} +#toc,.sidebarblock,.exampleblock>.content{background:none!important} +#toc{border-bottom:1px solid #ddddd8!important;padding-bottom:0!important} +.sect1{padding-bottom:0!important} +.sect1+.sect1{border:0!important} +#header>h1:first-child{margin-top:1.25rem} +body.book #header{text-align:center} +body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em 0} +body.book #header .details{border:0!important;display:block;padding:0!important} +body.book #header .details span:first-child{margin-left:0!important} +body.book #header .details br{display:block} +body.book #header .details br+span:before{content:none!important} +body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important} +body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always} +.listingblock code[data-lang]:before{display:block} +#footer{background:none!important;padding:0 .9375em} +#footer-text{color:rgba(0,0,0,.6)!important;font-size:.9em} +.hide-on-print{display:none!important} +.print-only{display:block!important} +.hide-for-print{display:none!important} +.show-for-print{display:inherit!important}} diff --git a/src/main/resources/docs/stylesheets/coderay-asciidoctor.css b/src/main/resources/docs/stylesheets/coderay-asciidoctor.css new file mode 100644 index 00000000000..ad53f53dc99 --- /dev/null +++ b/src/main/resources/docs/stylesheets/coderay-asciidoctor.css @@ -0,0 +1,89 @@ +/* Stylesheet for CodeRay to match GitHub theme | MIT License | http://foundation.zurb.com */ +/*pre.CodeRay {background-color:#f7f7f8;}*/ +.CodeRay .line-numbers{border-right:1px solid #d8d8d8;padding:0 0.5em 0 .25em} +.CodeRay span.line-numbers{display:inline-block;margin-right:.5em;color:rgba(0,0,0,.3)} +.CodeRay .line-numbers strong{color:rgba(0,0,0,.4)} +table.CodeRay{border-collapse:separate;border-spacing:0;margin-bottom:0;border:0;background:none} +table.CodeRay td{vertical-align: top;line-height:1.45} +table.CodeRay td.line-numbers{text-align:right} +table.CodeRay td.line-numbers>pre{padding:0;color:rgba(0,0,0,.3)} +table.CodeRay td.code{padding:0 0 0 .5em} +table.CodeRay td.code>pre{padding:0} +.CodeRay .debug{color:#fff !important;background:#000080 !important} +.CodeRay .annotation{color:#007} +.CodeRay .attribute-name{color:#000080} +.CodeRay .attribute-value{color:#700} +.CodeRay .binary{color:#509} +.CodeRay .comment{color:#998;font-style:italic} +.CodeRay .char{color:#04d} +.CodeRay .char .content{color:#04d} +.CodeRay .char .delimiter{color:#039} +.CodeRay .class{color:#458;font-weight:bold} +.CodeRay .complex{color:#a08} +.CodeRay .constant,.CodeRay .predefined-constant{color:#008080} +.CodeRay .color{color:#099} +.CodeRay .class-variable{color:#369} +.CodeRay .decorator{color:#b0b} +.CodeRay .definition{color:#099} +.CodeRay .delimiter{color:#000} +.CodeRay .doc{color:#970} +.CodeRay .doctype{color:#34b} +.CodeRay .doc-string{color:#d42} +.CodeRay .escape{color:#666} +.CodeRay .entity{color:#800} +.CodeRay .error{color:#808} +.CodeRay .exception{color:inherit} +.CodeRay .filename{color:#099} +.CodeRay .function{color:#900;font-weight:bold} +.CodeRay .global-variable{color:#008080} +.CodeRay .hex{color:#058} +.CodeRay .integer,.CodeRay .float{color:#099} +.CodeRay .include{color:#555} +.CodeRay .inline{color:#000} +.CodeRay .inline .inline{background:#ccc} +.CodeRay .inline .inline .inline{background:#bbb} +.CodeRay .inline .inline-delimiter{color:#d14} +.CodeRay .inline-delimiter{color:#d14} +.CodeRay .important{color:#555;font-weight:bold} +.CodeRay .interpreted{color:#b2b} +.CodeRay .instance-variable{color:#008080} +.CodeRay .label{color:#970} +.CodeRay .local-variable{color:#963} +.CodeRay .octal{color:#40e} +.CodeRay .predefined{color:#369} +.CodeRay .preprocessor{color:#579} +.CodeRay .pseudo-class{color:#555} +.CodeRay .directive{font-weight:bold} +.CodeRay .type{font-weight:bold} +.CodeRay .predefined-type{color:inherit} +.CodeRay .reserved,.CodeRay .keyword {color:#000;font-weight:bold} +.CodeRay .key{color:#808} +.CodeRay .key .delimiter{color:#606} +.CodeRay .key .char{color:#80f} +.CodeRay .value{color:#088} +.CodeRay .regexp .delimiter{color:#808} +.CodeRay .regexp .content{color:#808} +.CodeRay .regexp .modifier{color:#808} +.CodeRay .regexp .char{color:#d14} +.CodeRay .regexp .function{color:#404;font-weight:bold} +.CodeRay .string{color:#d20} +.CodeRay .string .string .string{background:#ffd0d0} +.CodeRay .string .content{color:#d14} +.CodeRay .string .char{color:#d14} +.CodeRay .string .delimiter{color:#d14} +.CodeRay .shell{color:#d14} +.CodeRay .shell .delimiter{color:#d14} +.CodeRay .symbol{color:#990073} +.CodeRay .symbol .content{color:#a60} +.CodeRay .symbol .delimiter{color:#630} +.CodeRay .tag{color:#008080} +.CodeRay .tag-special{color:#d70} +.CodeRay .variable{color:#036} +.CodeRay .insert{background:#afa} +.CodeRay .delete{background:#faa} +.CodeRay .change{color:#aaf;background:#007} +.CodeRay .head{color:#f8f;background:#505} +.CodeRay .insert .insert{color:#080} +.CodeRay .delete .delete{color:#800} +.CodeRay .change .change{color:#66f} +.CodeRay .head .head{color:#f4f} diff --git a/src/main/resources/docs/stylesheets/gh-pages.css b/src/main/resources/docs/stylesheets/gh-pages.css new file mode 100644 index 00000000000..121cac3885f --- /dev/null +++ b/src/main/resources/docs/stylesheets/gh-pages.css @@ -0,0 +1,214 @@ +@import url(https://fonts.googleapis.com/css?family=Montserrat|Open+Sans); +@import "asciidoctor.css"; /* Default asciidoc style framework - important */ + +/* Custom block: details */ + +.sidebarblock.details > .content { + border-left: .25rem solid rgba(0, 0, 0, 0.1); +} + +.sidebarblock.details > .content { + padding-left: .5rem +} + +.sidebarblock.details { + background-color: transparent; + border: none; + padding-bottom: 0; + padding-top: 0; +} + +/* Overrides for asciidoctor.css */ + +a { + color: #0074c7; +} + +h1, +#content h1 > a.link, +h2, +h2 > a.link, +h3, +h3 > a.link, +#toctitle, +#toctitle > a.link, +.sidebarblock > .content > .title, +.sidebarblock > .content > .title > a.link, +h4, +h4 > a.link, +h5, +h5 > a.link, +h6, +h6 > a.link { + color: #e46c0a; +} + +.subheader, +.admonitionblock td.content > .title, +.audioblock > .title, +.exampleblock > .title, +.imageblock > .title, +.listingblock > .title, +.literalblock > .title, +.stemblock > .title, +.openblock > .title, +.paragraph >.title, +.quoteblock > .title, +table.tableblock > .title, +.verseblock > .title, +.videoblock > .title, +.dlist > .title, +.olist > .title, +.ulist > .title, +.qlist > .title, +.hdlist > .title { + color: rgb(197, 90, 17); +} + +@media screen { + #footer { + background-color: #f6f6f6; + border-top: 1px #d2d2d2 solid; + border-bottom: 1px #d2d2d2 solid; + font-family: "Open Sans", "DejaVu Sans", sans-serif; + } + + #footer-text { + color: #595959; + line-height: 1; + } +} + +/* Utilities */ + +.container { + width: 100%; + max-width: 62.5rem; + margin-left: auto; + margin-right: auto; +} + +/* Colors */ + +.bg-light { + background-color: #f8f9fa; +} + +.bg-lighter { + background-color: #fbfbfb; +} + + +/* Navbar */ + +.navbar { + display: flex; + flex-wrap: nowrap; + justify-content: center; + font-family: "Open Sans", "DejaVu Sans", sans-serif; + font-size: 1rem; + padding: 0px 1rem; +} + +.navbar-lg { + font-size: 1.3rem; +} + +.navbar-light { + border-bottom: 1px #d2d2d2 solid; +} + +.navbar a { + text-decoration: none; +} + +.navbar-light a { + color: #595959; +} + +.navbar-light a:hover, +.navbar-light a:focus { + color: #000000; +} + +.navbar a.active, +.navbar a.active:hover, +.navbar a.active:focus { + font-weight: bold; +} + +.navbar-light a.active, +.navbar-light a.active:hover, +.navbar-light a.active:focus { + color: #000000; +} + +.navbar-light .nav-link { + border-bottom: 2px transparent solid; +} + +.navbar-light .nav-link.active { + border-bottom: 2px #e46c0a solid; +} + +.navbar-lg .nav-link.active { + border-bottom: 0; +} + +.navbar > .container { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.navbar-brand { + display: inline-block; + margin-right: 1rem; + padding: 0.8125rem 0rem; + padding-left: 0.9375rem; + font-size: 1.25rem; +} + +.navbar-brand img { + height: 1.4rem; + margin: 0rem 0.4rem; + padding: 0; + vertical-align: middle; +} + +.navbar-lg .navbar-brand { + font-size: 1.7rem; +} + +.navbar-lg .navbar-brand img { + height: 2.3rem; +} + +.navbar-nav { + display: flex; + flex-wrap: wrap; + flex-grow: 1; + align-items: center; + margin: 0px; + padding: 0px; + list-style: none; + line-height: inherit; +} + +.nav-link { + display: block; + margin: 0px; + border: 0px; + padding: 1rem 1rem; +} + +/* Do not display site header on print mediums */ +@media print { + #seedu-header { + display: none; + } + + #site-header { + display: none; + } +} diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png deleted file mode 100644 index 29810cf1fd9..00000000000 Binary files a/src/main/resources/images/address_book_32.png and /dev/null differ diff --git a/src/main/resources/images/black logo.png b/src/main/resources/images/black logo.png new file mode 100644 index 00000000000..67f1d5c6a82 Binary files /dev/null and b/src/main/resources/images/black logo.png differ diff --git a/src/main/resources/images/flashspeed_logo.png b/src/main/resources/images/flashspeed_logo.png new file mode 100644 index 00000000000..f7416eaa45c Binary files /dev/null and b/src/main/resources/images/flashspeed_logo.png differ diff --git a/src/main/resources/view/CardListPanel.fxml b/src/main/resources/view/CardListPanel.fxml new file mode 100644 index 00000000000..d294172e92e --- /dev/null +++ b/src/main/resources/view/CardListPanel.fxml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 09f6d6fe9e4..20db3efd6e3 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -2,8 +2,26 @@ - + + + + + + + - + + + + + + + + + + + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..06fff26479e 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -1,6 +1,6 @@ .background { - -fx-background-color: derive(#1d1d1d, 20%); - background-color: #383838; /* Used in the default.html file */ + -fx-background-color: derive(#928a97, 20%); + background-color: #ffffff; /* Used in the default.html file */ } .label { @@ -40,9 +40,9 @@ } .table-view { - -fx-base: #1d1d1d; - -fx-control-inner-background: #1d1d1d; - -fx-background-color: #1d1d1d; + -fx-base: #404040; + -fx-control-inner-background: #404040; + -fx-background-color: #404040; -fx-table-cell-border-color: transparent; -fx-table-header-border-color: transparent; -fx-padding: 5; @@ -77,46 +77,58 @@ } .split-pane:horizontal .split-pane-divider { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #4473c5; -fx-border-color: transparent transparent transparent #4d4d4d; } .split-pane { -fx-border-radius: 1; -fx-border-width: 1; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #4473c5; } .list-view { -fx-background-insets: 0; -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#ffffff, 20%); } .list-cell { -fx-label-padding: 0 0 0 0; -fx-graphic-text-gap : 0; -fx-padding: 0 0 0 0; + -fx-background-color: derive(#ffffff, 20%); } .list-cell:filled:even { - -fx-background-color: #3c3e3f; + -fx-background-color: #f4b184; + -fx-border-color: #ffffff; + -fx-border-width: 1; } .list-cell:filled:odd { - -fx-background-color: #515658; + -fx-background-color: #f4b184; + -fx-border-color: #ffffff; + -fx-border-width: 1; } .list-cell:filled:selected { -fx-background-color: #424d5f; + -fx-border-color: #ffffff; + -fx-border-width: 1; + } .list-cell:filled:selected #cardPane { - -fx-border-color: #3e7b91; + -fx-border-color: #ffffff; -fx-border-width: 1; } .list-cell .label { + -fx-text-fill: black; +} + +.list-cell:filled:selected .label { -fx-text-fill: white; } @@ -133,24 +145,30 @@ } .stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#ffffff, 20%); } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #4473c5; + -fx-border-color: derive(#1d1d1d, 10%); + -fx-border-top-width: 1px; +} + +.pane-with-border-2 { + -fx-background-color: #4473c5; -fx-border-color: derive(#1d1d1d, 10%); -fx-border-top-width: 1px; } .status-bar { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: white; } .result-display { -fx-background-color: transparent; -fx-font-family: "Segoe UI Light"; - -fx-font-size: 13pt; - -fx-text-fill: white; + -fx-font-size: 15pt; + -fx-text-fill: black; } .result-display .label { @@ -159,8 +177,8 @@ .status-bar .label { -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; - -fx-padding: 4px; + -fx-text-fill: black; + -fx-padding: 2px; -fx-pref-height: 30px; } @@ -185,7 +203,7 @@ } .context-menu { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: derive(white, 50%); } .context-menu .label { @@ -193,13 +211,13 @@ } .menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #fff; } .menu-bar .label { -fx-font-size: 14pt; -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; + -fx-text-fill: black; -fx-opacity: 0.9; } @@ -282,7 +300,7 @@ } .scroll-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: white; } .scroll-bar .thumb { @@ -325,15 +343,15 @@ -fx-border-width: 1; -fx-font-family: "Segoe UI Light"; -fx-font-size: 13pt; - -fx-text-fill: white; + -fx-text-fill: black; } -#filterField, #personListPanel, #personWebpage { +#filterField, #deckListPanel, #deckPage { -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); } #resultDisplay .content { - -fx-background-color: transparent, #383838, transparent, #383838; + -fx-background-color: transparent, #ffffff, transparent, #ffffff; -fx-background-radius: 0; } diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/DeckListCard.fxml similarity index 82% rename from src/main/resources/view/PersonListCard.fxml rename to src/main/resources/view/DeckListCard.fxml index f08ea32ad55..6f8dfda23fc 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/DeckListCard.fxml @@ -8,7 +8,6 @@ - @@ -26,11 +25,16 @@ - -