diff --git a/.gitignore b/.gitignore index 823d175eb670..6182327d63ce 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,8 @@ lib/* *.iml *.log *.log.* -*.csv -config.json +/*.csv +/config.json src/test/data/sandbox/ preferences.json .DS_Store diff --git a/README.adoc b/README.adoc old mode 100644 new mode 100755 index b4c8aac388ae..a4518d4b2f2b --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,9 @@ -= Address Book (Level 4) += Know-It-All ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] -https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] +https://travis-ci.com/cs2103-ay1819s2-w10-4/main[image:https://travis-ci.com/cs2103-ay1819s2-w10-4/main.svg?branch=master[Build Status]] +https://ci.appveyor.com/project/dlqs/main-q3u06[image:https://ci.appveyor.com/api/projects/status/t9v6c7uxe5ci5n3g/branch/master?svg=true[Build status]] +https://coveralls.io/github/cs2103-ay1819s2-w10-4/main?branch=master[image:https://coveralls.io/repos/github/cs2103-ay1819s2-w10-4/main/badge.svg?branch=master[Coverage Status]] ifdef::env-github[] image::docs/images/Ui.png[width="600"] @@ -15,26 +13,68 @@ ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +ifdef::env-github[] +:imagesdir: https://raw.githubusercontent.com/cs2103-ay1819s2-w10-4/main/master/docs/images +endif::[] + +{nbsp} + +Having a hard time memorizing _two hundred pages_ worth of lecture notes? + +Can't recall the difference between the "ATP" and "ADP"? Or remember the name of the "leg bone"? + +The fact is, _memory work is hard_. *And we understand.* + +Introducing... *_Know-It-All_*! + +*_Know-It-All_* is targeted at medical students (in fact, any students) as their studies requires a considerable amount of +memory work, and deals with content that is suitable for bite-sized flashcard format. + +*_Know-It-All_* lets you: + + * manage your flash cards, test yourself, and even share flashcards with others + * view your cards through the GUI (Graphical User Interface) + * have maximum control through the CLI (Command Line Interface) + +*_Know-It-All_* uses _innovative_ teaching methods to ensure our users love the learning process, combined with scientifically proven techniques such as _spaced-repetition_ to drive their performance ahead of the curve. + +Compiled by a fleet of genuises, *_Know-It-All_* has been used by literally over a dozen students who have seen improvements in their results. Here are some raving reviews: + +{nbsp} + +[quote, anonymous medical student] +Saved my life. Now I know the difference between the leg bone and the thigh bone! + +{nbsp} + +[quote, The Singapore Flashcard Journal] +Anki should be shaking in their boots right now. ⭐⭐⭐⭐⭐ + +{nbsp} + +[quote, Singapore Business Satisfaction Review] +At last, a new high in BS - has been achieved. + +{nbsp} + +What are you waiting for? Download *_Know-It-All_* today! + +{nbsp} + +*_Know-It-All_* is proud to present the following _awards_: + +ifdef::env-github[] +image:award1.png[width="200"] {nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}image:award2.png[width="200"] +endif::[] + +ifndef::env-github[] +image:images/award1.png[width="200"] {nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}image:images/award2.png[width="200"] +endif::[] == Site Map * <> * <> -* <> * <> * <> == Acknowledgements -* Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by -_Marco Jakob_. +* This application is a fork of the https://github.com/se-edu/addressbook-level4[AddressBook-Level4 project] created by the https://github.com/se-edu/[SE-EDU initiative] * Libraries used: https://github.com/TestFX/TestFX[TextFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit5[JUnit5] == Licence : link:LICENSE[MIT] diff --git a/_reposense/config.json b/_reposense/config.json new file mode 100644 index 000000000000..edf41136e568 --- /dev/null +++ b/_reposense/config.json @@ -0,0 +1,29 @@ +{ + "authors": [ + { + "githubId": "afterdusk", + "displayName": "AU ...JUN", + "authorNames": ["Liang Jun Au", "Liang Jun", "afterdusk"] + }, + { + "githubId": "dlqs", + "displayName": "DON...ANG", + "authorNames": ["Donald", "dlqs"] + }, + { + "githubId": "KerrynEer", + "displayName": "KER...EER", + "authorNames": ["KerrynEer"] + }, + { + "githubId": "mmdlow", + "displayName": "MAT... DE", + "authorNames": ["Matthew Low", "mmdlow"] + }, + { + "githubId": "yichong96", + "displayName": "ONG...ONG", + "authorNames": ["Yi Chong Ong", "yichong96"] + } + ] +} diff --git a/build.gradle b/build.gradle old mode 100644 new mode 100755 index 4f2949b6e774..450f9c67ac54 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ if (JavaVersion.current() == JavaVersion.VERSION_1_10 } // Specifies the entry point of the application -mainClassName = 'seedu.address.MainApp' +mainClassName = 'seedu.knowitall.MainApp' sourceCompatibility = JavaVersion.VERSION_1_9 targetCompatibility = JavaVersion.VERSION_1_9 @@ -77,7 +77,7 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'knowitall.jar' destinationDir = file("${buildDir}/jar/") } @@ -152,16 +152,16 @@ test { } if (runNonGuiTests) { - test.include 'seedu/address/**' + test.include 'seedu/knowitall/**' } if (runGuiTests) { test.include 'systemtests/**' - test.include 'seedu/address/ui/**' + test.include 'seedu/knowitall/ui/**' } if (!runGuiTests) { - test.exclude 'seedu/address/ui/**' + test.exclude 'seedu/knowitall/ui/**' } } } @@ -202,9 +202,8 @@ asciidoctor { idprefix: '', // for compatibility with GitHub preview idseparator: '-', 'site-root': "${sourceDir}", // must be the same as sourceDir, do not modify - 'site-name': 'AddressBook-Level4', - 'site-githuburl': 'https://github.com/se-edu/addressbook-level4', - 'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) + 'site-name': 'Know-It-All', + 'site-githuburl': 'https://github.com/cs2103-ay1819s2-w10-4/main', ] options['template_dirs'].each { diff --git a/config/travis/check-eof-newline.sh b/config/travis/check-eof-newline.sh index b771f3988dd8..9dd231bbfe94 100755 --- a/config/travis/check-eof-newline.sh +++ b/config/travis/check-eof-newline.sh @@ -3,14 +3,14 @@ ret=0 -# Preserve filename with spaces by only splitting on newlines. +# Preserve csvFile with spaces by only splitting on newlines. IFS=' ' -for filename in $(git grep --cached -I -l -e '' -- ':/'); do - if [ "$(tail -c 1 "./$filename")" != '' ]; then - line="$(wc -l "./$filename" | cut -d' ' -f1)" - echo "ERROR:$filename:$line: no newline at EOF." +for csvFile in $(git grep --cached -I -l -e '' -- ':/'); do + if [ "$(tail -c 1 "./$csvFile")" != '' ]; then + line="$(wc -l "./$csvFile" | cut -d' ' -f1)" + echo "ERROR:$csvFile:$line: no newline at EOF." ret=1 fi done diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc old mode 100644 new mode 100755 index e647ed1e715a..dd5251a2217a --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,53 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + +Know-It-All was developed by the AY2018/19 Semester 2 CS2103T W10-4 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]] [<>] +=== Au Liang Jun +image::afterdusk.png[width="150", align="left"] +{empty}[https://github.com/afterdusk[github]] [<>] -Role: Project Advisor +Role: Team Lead + +Responsibilities: Folder features ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Donald Lee Qian Siang +image::dlqs.png[width="150", align="left"] +{empty}[https://github.com/dlqs[github]] [<>] -Role: Team Lead + -Responsibilities: UI +Role: Developer + +Responsibilities: Report and Scoring features ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== Kerryn Eer +image::kerryneer.png[width="150", align="left"] +{empty}[https://github.com/KerrynEer[github]] [<>] Role: Developer + -Responsibilities: Data +Responsibilities: Test session features ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Matthew Low Min De +image::mmdlow.png[width="150", align="left"] +{empty}[https://github.com/mmdlow[github]] [<>] Role: Developer + -Responsibilities: Dev Ops + Threading +Responsibilities: Card features ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Ong Yi Chong +image::yichong96.png[width="150", align="left"] +{empty}[https://github.com/yichong96[github]] [<>] Role: Developer + -Responsibilities: UI +Responsibilities: Advanced features ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 5de5363abffd..fc1ed87be8f6 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -2,6 +2,6 @@ :site-section: ContactUs :stylesDir: stylesheets -* *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level4/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. +* *Bug reports, Suggestions* : Post in our https://github.com/cs2103-ay1819s2-w10-4/main/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. * *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] -* *Email us* : You can also reach us at `damith [at] comp.nus.edu.sg` +* *Email us* : You can also reach us at `au.liangjun [at] comp.nus.edu.sg` diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc old mode 100644 new mode 100755 index 8b92d5fb7e62..8fcce9114f7b --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,8 +1,9 @@ -= AddressBook Level 4 - Developer Guide += Know-It-All - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: :toc-placement: preamble +:toclevels: 3 :sectnums: :imagesDir: images :stylesDir: stylesheets @@ -13,9 +14,9 @@ ifdef::env-github[] :warning-caption: :warning: :experimental: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/cs2103-ay1819s2-w10-4/main -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `CS2103-AY2018/19s2-W10-4 Team` Since: `Mar 2019` Licence: `MIT` == Setting up @@ -47,14 +48,15 @@ Do not disable them. If you have disabled them, go to `File` > `Settings` > `Plu . Click `OK` to accept the default settings . Open a console and run the command `gradlew processResources` (Mac/Linux: `./gradlew processResources`). It should finish with the `BUILD SUCCESSFUL` message. + This will generate all resources required by the application and tests. -. Open link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow.java`] and check for any code errors +. Open link: https://github.com/cs2103-ay1819s2-w10-4/main/blob/master/src/main/java/seedu/knowitall/ui/MainWindow.java[`MainWindow.java`] and check for any code errors .. Due to an ongoing https://youtrack.jetbrains.com/issue/IDEA-189060[issue] with some of the newer versions of IntelliJ, code errors may be detected even if the project can be built and run successfully .. To resolve this, place your cursor over any of the code section highlighted in red. Press kbd:[ALT + ENTER], and select `Add '--add-modules=...' to module compiler options` for each error -. Repeat this for the test folder as well (e.g. check link:{repoURL}/src/test/java/seedu/address/ui/HelpWindowTest.java[`HelpWindowTest.java`] for code errors, and if so, resolve it the same way) +. Repeat this for the test <> as well (e.g. check link: https://github.com/cs2103-ay1819s2-w10-4/main/blob/master/src/main/java/seedu/knowitall/ui/HelpWindow.java[`HelpWindowTest.java`] for code errors, and +if so, resolve it the same way) === Verifying the setup -. Run the `seedu.address.MainApp` and try a few commands +. Run the `seedu.knowitall.MainApp` and try a few commands . <> to ensure they all pass. === Configurations to do before writing code @@ -74,13 +76,15 @@ Optionally, you can follow the <> docume ==== Updating documentation to match your fork -After forking the repo, the documentation will still have the SE-EDU branding and refer to the `se-edu/addressbook-level4` repo. +After forking the repo, the documentation will still have the SE-EDU branding and refer to the +`cs2103-ay1819s2-w10-4/main` repo. -If you plan to develop this fork as a separate product (i.e. instead of contributing to `se-edu/addressbook-level4`), you should do the following: +If you plan to develop this fork as a separate product (i.e. instead of contributing to `cs2103-ay1819s2-w10-4/main`) +, you should do the following: . Configure the <> in link:{repoURL}/build.gradle[`build.gradle`], such as the `site-name`, to suit your own project. -. Replace the URL in the attribute `repoURL` in link:{repoURL}/docs/DeveloperGuide.adoc[`DeveloperGuide.adoc`] and link:{repoURL}/docs/UserGuide.adoc[`UserGuide.adoc`] with the URL of your fork. +. Replace the URL in the attribute `repoURL` in link: https://github.com/cs2103-ay1819s2-w10-4/main/blob/master/docs/DeveloperGuide.adoc[`DeveloperGuide.adoc`] and link: https://github.com/cs2103-ay1819s2-w10-4/main/blob/master/docs/UserGuide.adoc[`UserGuide.adoc`] with the URL of your fork. ==== Setting up CI @@ -98,10 +102,7 @@ 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, - -1. Get some sense of the overall design by reading <>. -2. Take a look at <>. +When you are ready to start coding, get some sense of the overall design by reading <>. == Design @@ -114,9 +115,9 @@ image::Architecture.png[width="600"] The *_Architecture Diagram_* given above explains the high-level design of the App. Given below is a quick overview of each component. [TIP] -The `.pptx` files used to create diagrams in this document can be found in the link:{repoURL}/docs/diagrams/[diagrams] folder. To update a diagram, modify the diagram in the pptx file, select the objects of the diagram, and choose `Save as picture`. +The `.pptx` files used to create diagrams in this document can be found in the link:https://github.com/cs2103-ay1819s2-w10-4/main/tree/master/docs/diagrams[diagrams] folder. To update a diagram, modify the diagram in the pptx file, select the objects of the diagram, and choose `Save as picture`. -`Main` has only one class called link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp`]. It is responsible for, +`Main` has only one class called link: https://github.com/cs2103-ay1819s2-w10-4/main/blob/master/src/main/java/seedu/knowitall/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. @@ -149,26 +150,33 @@ image::LogicClassDiagram.png[width="800"] The _Sequence Diagram_ below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. .Component interactions for `delete 1` command -image::SDforDeletePerson.png[width="800"] +image::SDForDeleteCard.png[width="800"] The sections below give more details of each component. - +//tag::UIdesign[] [[Design-Ui]] === UI component .Structure of the UI Component image::UiClassDiagram.png[width="800"] -*API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] +*API* : https://github.com/cs2103-ay1819s2-w10-4/main/blob/master/src/main/java/seedu/knowitall/ui/Ui.java[`Ui.java`] -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. +`UI` consists of a `MainWindow` made up of many parts as seen above e.g.`CommandBox`, `ResultDisplay`, `CardMainScreen`, +`StatusBarFooter`, `BrowserPanel` etc. All these parts, including the `MainWindow`, inherit from the abstract `UiPart` +class. +//end::UIdesign[] -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 located in the `src/main/resources/view` folder. +For example, the layout of the link: https://github.com/cs2103-ay1819s2-w10-4/main/blob/master/src/main/java/seedu/knowitall/ui/MainWindow.java[`MainWindow`] is +specified in link: https://github.com/cs2103-ay1819s2-w10-4/main/blob/master/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. +* Uses `Logic` component to execute user commands. +* Listens for changes to `Model` data to update UI with the modified data. + [[Design-Logic]] === Logic component @@ -177,37 +185,40 @@ The `UI` component, .Structure of the Logic Component image::LogicClassDiagram.png[width="800"] -*API* : -link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] +*API* : https://github.com/cs2103-ay1819s2-w10-4/main/blob/master/src/main/java/seedu/knowitall/logic/Logic.java[`Logic.java`] -. `Logic` uses the `AddressBookParser` class to parse the user command. +. `Logic` uses the `CardFolderParser` 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 -image::DeletePersonSdForLogic.png[width="800"] +image::DeleteCardSdForLogic.png[width="800"] +//tag::modeldesign[] [[Design-Model]] === Model component .Structure of the Model Component image::ModelClassDiagram.png[width="800"] -*API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] +*API* : https://github.com/cs2103-ay1819s2-w10-4/main/blob/master/src/main/java/seedu/knowitall/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 a list of `VersionedCardFolders` representing the folders that the user has. These `VersionedCardFolders` in turn each store a list of `Cards`. Each `Card` stores information about a single question. +* exposes unmodifiable instances of `FilteredList` and `FilteredList` 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. +* exposes two instances of `SimpleObjectProperty`, which represent the current cards being selected and being tested, if any. * does not depend on any of the other three components. +//end::modeldesign[] + [NOTE] -As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` can reference. This would allow `Address Book` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object. An example of how such a model may look like is given below. + +As a more OOP model, we can change the `Card` implementation to be that of a parent class, from which 2 subclasses, `SingleAnswerCard` and `McqCard` can inherit from. This would eliminate the need for the `Card` class to maintain a Set of MCQ `Options` even if it is a Single-answer card. An example of how such a model may look like is given below. + + image:ModelClassBetterOopDiagram.png[width="800"] @@ -217,74 +228,945 @@ image:ModelClassBetterOopDiagram.png[width="800"] .Structure of the Storage Component image::StorageClassDiagram.png[width="800"] -*API* : link:{repoURL}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] +*API* : https://github.com/cs2103-ay1819s2-w10-4/main/blob/master/src/main/java/seedu/knowitall/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. It maintains a single instance of `UserPrefStorage`. +* can save `CardFolder` data in json format and read it back. It holds a list of `CardFolderStorage` objects, each handling the storage functions of a single `CardFolder`. [[Design-Commons]] === Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `seedu.knowitall.commons` package. == Implementation This section describes some noteworthy details on how certain features are implemented. +// tag::cards[] +=== Cards +==== Current Implementation +The `Card` is one of the core aspects of the application. Cards are the result of morphing the `Person` class from the original AddressBook model. This implementation incorporates the `Model` and `Logic` components. + +[discrete] +==== Model +To allow users to manage Cards, the following methods are available via the `Model` component: + +* `ModelManager#addCard(Card card)` - Adds a new card to the currently active `VersionedCardFolder` folder +* `ModelManager#setCard(Card target, Card editedCard)` - Edits the information of a target card in the currently active folder +* `ModelManager#deleteCard(Card target)` - Deletes the target card from the currently active folder +* `ModelManager#hasCard(Card card)` - Checks if a card is already present in the currently active folder + +[discrete] +==== Logic +As with all other commands, the `LogicManager#execute(String commandText)` method of the `Logic` component parses the user's input, say a command to add a new card, and executes the corresponding `Command`. + +[discrete] +==== Example Usage + +The following steps detail the underlying logic executed when a user does a card-related operation, say an add card operation. + +1. User is in the `Organs` folder and wants to add a new card, with question 'What is the largest organ?' and answer 'Skin'. This is done by typing `add q/What is the largest organ? a/Skin`. ++ +image:CardImplementationAddExample.png[width="800"] + +2. The command parser reads the string input (as entered by the user) and returns the corresponding `Command`, an `AddCommand` object in this instance. + +3. Upon execution, the `AddCommand` checks if the card to be added is already present in the current folder. If so, an exception is thrown. + +4. The `AddCommand` then calls the `ModelManager#addCard(Card card)` method. + +5. The new card will then be added to the active `VersionedCardFolder`. + +6. If the user is not inside a folder, or if the card to add already exists inside the current folder, the `addCommand` will throw a `CommandException`. + +The following sequence diagram demonstrates how `AddCommand` works. + +image:AddCommandSequenceDiagram.png[width="800"] + +==== Design Considerations + +===== Aspect: How to represent options for MCQ cards + +* **Alternative 1 (current choice):** Maintain a set of `Option` objects to represent incorrect options, separate from the `Answer` field of each `Card`. +** Pros: Simple to implement, easy to convert the card type between MCQ and Single-answer, requires the least amount of implementation changes to `Card`. +** Cons: Single-answer cards still have to maintain an empty `Option` set. +* **Alternative 2:** Maintain 2 separate subclasses of `Card`, one for Single-answer and another for MCQ. +** Pros: More object-oriented implementation. +** Cons: Harder to implement. + +// end::cards[] + +// tag::score[] +=== Score feature +==== Current Implementation + +To implement tracking of the number of correct and incorrect attempts, a new attribute `Score` was added to `Card`. +Score keeps track of both numbers. +This attribute is encapsulated in the `Score` class. + +==== Design Considerations + +===== Aspect: How to represent score + +* **Alternative 1 (current choice):** Track total number of correct attempts and total number of attempts +** Pros: Simple to implement. Most semantically correct. +** Cons: Score will be rolling average. If the question is answered wrongly even once, the score can never be 100%. +* **Alternative 2:** Track only last X attempts. +** Pros: An improvement in performance will be more obvious. +** Cons: Uses more memory. Have to delete the X+1th score every time a new score is added. + +===== Aspect: How to read/write score from file + +* **Alternative 1 (current choice):** Read/write as `String`. +** Pros: `String`s are easier to read/write to file. +** Cons: There must be strict checks when instantiating score from strings as they are prone to many kinds of formatting errors. +* **Alternative 2:** Read/write as a double. +** Pros: A `double` can represent both numbers with just one, which then can be converted to `String`. +** Cons: When instantiating score from double, it might be simplified. +For example, 2 correct attempts and 4 total attempts becomes 0.5. When instantiating from double, it is interpreted as 1 correct attempt and 2 total attempts. +* **Alternative 3:** Read and write both numbers as integers. +** Pros: Most correct implementation. +** Cons: Reading/writing to file now needs to take into account this fact. The `toString()` method cannot be used to write to file. + +// end::score[] + + +// tag::report[] +=== Report feature +==== Current Implementation + +The report feature is meant to provide the user with the ability to look back and compare their test score from +previous test sessions. It makes use of Java FXML features to display scores in a user-friendly manner. The following +methods are used to enter or exit the report display: + +* `Model#enterReportDisplay()` -- Enters the report display for this folder. +* `Model#exitReportDisplay()` -- Exits the report display back into the folder. + +Here is a screenshot of how it looks: + +image:ReportDisplay.png[width="800"] + +Given below is an example usage scenario and how the report display is rendered. + +Step 1. The user is in a folder and wishes to see a report by executing `report`. + +Step 2. A `ReportCommand` is created and executed by `LogicManager`. `Model#enterReportDisplay()` is called, which prepares +the model for report by sorting the cards by lowest scores first. The state is also checked to make sure the user is in +a folder. + +Step 3. A `CommandResult` with type `ENTERED_REPORT` is returned by the `ReportCommand`. + +Below is a sequence diagram, summarizing the above operations. + +.Sequence Diagram for Report Command +image:ReportCommandSequenceDiagram.png[width="800"] + +Step 4. The `CommandResult` is passed to UI, which now has to render the new screen. It does so by getting the current + `CardFolder` from Model, then instantiating a `ReportDisplay` with it. `ReportDisplay` prepares several elements for + display by Java FXML, split by their content: + + * `ReportDisplay#displayTitle()` -- Displays title and how many test scores were recorded. + * `ReportDisplay#displayGraph()` -- Takes maximum of last 10 test scores from `CardFolder`, and + adds them as points on the Java FXML `AreaChart`. + * `ReportDisplay#displayQuestions()` -- Displays maximum of 3 questions from `CardFolder` which have lowest the + individual <>. + * `ReportDisplay#displayTagline()` -- Calculates change in test score from last and second last test session + (if available) and formats it in a user-friendly manner. + +Step 5. The `ReportDisplay` is rendered by `fullScreenPlaceholder`. + +Below is a sequence diagram summarizing steps 4 and 5. The interactions with Java FXML elements are omitted for clarity. + +.Sequence diagram for Report Display +image:ReportDisplaySequenceDiagram.png[width="800"] + +==== Design Considerations + +===== Aspect: How to display test scores + +* **Alternative 1 (current choice):** Display previous test scores in a graph and lowest scoring cards +** Pros: Has benefits of seeing the graph as well as knowing which cards the user needs help in. +** Cons: More performance and memory intensive. Screen may get messy if there are too many items. +* **Alternative 2:** Display previous test scores in a graph +** Pros: More visual, easier to see change in test score. +** Cons: More performance and memory intensive as a graph needs to be rendered. Remedy: Display only last ten. +* **Alternative 3:** List the previous test scores +** Pros: User can see more accurate numbers. They can also see the individual card scores, so that +they can tell where they need help. +** Cons: Hard to see change from test session to test session. + +===== Aspect: Where to display test scores +* **Alternative 1 (current choice):** Display in full screen, entered from card folder +** Pros: Works because the test scores are scored by card folders. +** Cons: Need to implement a new state for commands, because it should not be possible to do some e.g. add card while +in fullscreen report. +* **Alternative 2:** Display in right panel, with cards on the left +** Pros: User can see all their cards at the same time. +** Cons: Less space to render report details such as graph. + +// end::report[] + +// tag::state[] +=== State +==== Current Implementation +===== Model +Previously, Model did not have a concept of state as there was only one screen where the user could be. But +as Know-It-All grew, there are more screens that a user can be in and more commands that can only be executed in certain +screens. Thus there is a need to manage the state in Model. + +==== Design Considerations + +===== Aspect: How to manage state + +* **Alternative 1 (current choice):** Use enum of States +** Pros: Fixes the states that Model can be in. By design, only one state can be true at any point in time, if state is set to type State which is a enum. +** Cons: Need to add new states to enum. +* **Alternative 2:** Use Strings +** Pros: No need to define new states. Trivial to change into new state: Simply set state to "folder", or "homeDir". Easy to check as well, e.g. to Check state == "folder". +** Cons: Becomes very unsafe as even a typo would mean entering a state that other parts of the application would not understand. +e.g. Setting state to "Folder" instead of "folder". +* **Alternative 2:** Use boolean flags +** Pros: Easy to manipulate. Clear when there are only two states. +** Cons: Becomes very messy when there are more states, since there is a need to ensure that only one boolean flag +is true at any point in time. E.g., only one of inFolder, inHomeDir, inTest... can be true. + +// end::state[] + +//tag::importexport[] +=== Import/Export feature +[discrete] +==== Current Implementation +The Import and Export feature is facilitated by the Logic and model components, together +with the addition of 3 new classes inside the csvmanager package defined under +the storage package. + +[discrete] +===== Logic +Similar to how the current commands are implemented, where each specific command inherits from the abstract +class command, the Import and Export command likewise inherit from the command class. + + +[discrete] +===== Model +The model component supports the addition of a new object attribute `CsvManager`, that carries out the read and +write logic of cards to external files. + +Additionally, model implements two new methods, `Model#importCardFolders` and `Model#exportCardFolders`. +These two methods further call the CsvManager API to read and write card folders. + +Model also implements `Model#setTestCsvPath` and the `Model#getDefaultPath` which are only used during testing. + +[discrete] +===== csvmanager package: +The csvmanager package contains all classes dealing with the reading and writing of card folders into and out to +the model. + +The classes include: + +* `CsvManager` - Main class responsible for the writing and reading of .csv files during the import and export of files +* `CsvFile` - Wrapper class for file name input by user +* `CsvCommands` interface - API for import/export method in CsvManager +* `CsvUtils` - Utility class containing the default path and test paths used by the application. + +The main logic for the import / export command is carried out inside the CsvManager class specified by it's +API `CsvManager#writeFoldersToCsv` and `CsvManager#readFoldersToCsv`. + + +[discrete] +===== Example Usage +1. User wants to export folders Human Anatomy and Nervous System. +Suppose that these two folders index as shown on the main application screen is 1 and 3 respectively. +The user types ``export 1 3`` + +2. The Export command parser extracts the indices, stores the indices into a `List` and +`ExportCommand` object, which keeps track of the indices. + + +3. The Export command calls executes, resulting in a call to the `Model#exportCardFolders` method. + +4. `Model#exportCardFolders` method checks that all card folders specified by user exists within the model. + With reference to the diagram below, we can see that the indices specified by the user (1 and 3) corresponds to the + (i - 1) th indexes of the list of card folders in the model, + where 1 < i <= no.of card folders in model. + + The method throws `CardFolderNotFoundException` if card folder index is not found in list. ++ +image::import_export_model_list1.png[width = "800"] + + +5. `CsvManager` is called to carry out the main logic of writing the card folders to file. + File exported will be created in the project root directory. + + Names of the files created will correspond to the names of their corresponding card folders. + + i.e `Human Anatomy.csv` and `Nervous System.csv` + +6. User wants to import `Human Anatomy.csv` file. +`Human Anatomy.csv` file contains flashcards represented in csv file format. +User types `import Blood.csv` command + +7. Import command parser extracts file name, wraps file into a `CsvFile` object and + parses the `CsvFile` object into an Import Command object. + +8. Logic unit executes the import command. +The execute method makes a call to `Model#importCardFolder` method. The model checks that +the card folder to import does not already exist within the model. Throws `DuplicateCardFolderException` +if card folder already exists. + +9. CsvManager is called to carry out the main logic of reading card folders to file. +File imported will be serialized into a `CardFolder` object and +added to the `folders` variable in model. + + +NOTE: Both Imported and Exported files have to be placed in the project root directory. + +The below diagram shows the sequence diagram for the Import/Export commands + +image::import_export_sequenceDiagram.png[width = "1000"] + +==== CSV file structure +Example of a common cardfolder csv file, opened using microsoft excel. + + +Blood.csv + +image::Blood.png[width= '800'] + +* The first line of any file contains the headers for each card. +These headers have to be present in the csv file. If not the import would not work. + +* Each row of the csv file starting from the second, +represents a single flashcard. + +* Option headers i.e (Option,Option,Option) +can be left blank or take on 1 value. + +* Hints header can take either 0 or 1 value. + +//end::importexport[] + + + +==== Design Considerations +//tag::design_considerations_import_export[] + +===== Aspect: Which component responsible for import/export logic +* **Alternative 1 (current choice)**: Implement read and write card folders in StorageManager class +** Pros: The most intuitive solution, +since Storage involves read and write logic +** Cons: +*** Either Logic and Model will be more coupled with storage. Storage also has more than one responsibility now. +* **Alternative 2**:Implement read/write and other logic in another class. (Current) +** Pros: Separate responsibilities of both Storage and Model. Model class can focus on the representation of the in-memory card folders +and Storage class can focus on managing the internal card folder data (.json files) +** Cons: More code to write. Storage class could possibly call the relevant API's +to convert .json file into .csv file + + +===== Aspect: Csv file design structure +* **Alternative 1**: Export multiple card folders into a single file. + +** Pros: Saves user trouble of calling multiple import for files. + +Each card folder is separated by a new line. +** Cons: Not a .csv file anymore. First line header would now specify cardfolder name before card headers, +leading to unequal rows and columns +* **Alternative 2**: Export each card folder into a single file (Current) +** Pros: More flexibility for users to import desired card folders, +since 1 cardfolder = 1 csv file. Files are also now correctly formatted as .csv file +and Storage class can focus on managing the internal card folder data (.json files) +** Cons: Slightly more work needed to import multiple card folders. +//end::design_considerations_import_export[] + + +//tag::folders[] +=== Folders +==== Current Implementation +A folder is another layer of abstraction over a `CardFolder`. Where we dealt with a single `CardFolder` in previous iterations, we now have multiple `CardFolders` that each have their own set of `Cards`. Users are able to manage each `CardFolder` independently. + +Folders in the application are achieved via enhancements from the AddressBook implementation. The changes span across all four components (UI, Logic, Model and Storage). + +===== Model +Previously, an instance of `ModelManager` contained only a single `VersionedCardFolder`, holding the current and previous state of the `CardFolder`. To support multiple folders, `ModelManager` now holds an `ObservableList` of `CardFolders`. The change is illustrated in the figure below, with the original implementation on the left and new implementation on the right. + +image::ModelEnhancementDiagram.png[width="800"] + +To allow users to operate on multiple `CardFolders`, the following notable methods were also introduced: + +* `ModelManager#addFolder(CardFolder cardfolder)` - Adds a specified cardfolder to the `ModelManager`’s list +* `ModelManager#deleteFolder(int index)` - Deletes the `CardFolder` at the specified index in the `ModelManager's` list +* `ModelManager#getActiveCardFolderIndex()` - Gets the index of the current active `CardFolder` +* `ModelManager#enterFolder(int index)` - Specifies the active `CardFolder` for operations to be performed on via the index in `ModelManager`’s list and sets the boolean `inFolder` to `true` to denote that user is inside a folder. +* `ModelManager#exitFolderToHome()` - Sets the boolean `inFolder` to `false` to indicate that the user is at the home directory. +* `ModelManager#renameFolder(int index, String newName)` - Renames the folder at the specified index in the `ModelManager's` list to the new name. +* `ModelManager#getState()` - Returns the current state of the `ModelManager`. The possible states are {`IN_FOLDER`, `IN_HOMEDIR`, `IN_TEST`, `IN_REPORT`}. + +===== Storage +Similarly, the `StorageManager` needs to represent each `CardFolder` separately. In the same manner as in the Model component, we introduce a list of `JsonCardFolderStorages`. The change is illustrated in the figure below, with the original implementation on the left and new implementation on the right. + +image::StorageEnhancementDiagram.png[width="800"] + +Notable new methods: + +* `StorageManager#readCardFolders()` - Reads in all `CardFolders` from all `CardFolderStorage` objects in the list. +* `StorageManager#saveCardFolders(List cardFolders)` - Saves all `CardFolders` provided in the argument to the user's data directory. + +===== Logic +The existing implementation of the Logic component propagates changes in a Model's `CardFolder` to the Storage component. With listeners, it is informed when a `CardFolder` is modified (e.g. a new card is added) so that it can invoke the appropriate Storage methods. + +The same principle was applied to propagate changes regarding `CardFolders` themselves (and not their stored cards) to Storage: e.g. adding a new folder. Model is now an `Observable`, and changes to a Model’s `CardFolders` will inform the `LogicManager`, which in turn invokes `StorageManager#saveCardFolders(List cardFolders)`. + +To illustrate how the Model, Storage and Logic components interact, below is a walkthrough of a typical usage scenario of the `addfolder` command. <> is a sequence diagram that summarises the example: + +* Step 1. The `addfolder` command is executed. For example, `addfolder f`. + +* Step 2. As with every command, the command parser reads the input and generates the relevant `Command` object, in this case an `AddFolderCommand`. The object is returned to the `LogicManager`. + +[NOTE] +If the input is invalid (e.g. user did not provide a folder name or provided one that violated the constraints of folder names), Step 2 would not proceed and an error message is displayed. The Model and Storage components would not be modified. + +* Step 3. The `LogicManager` executes the `AddFolderCommand`, Before transferring control to the Model component with the `ModelManager#addFolder()` method, a few checks are performed to ensure that the user is inside a folder and the model does not already have a folder with the same name (not shown in sequence diagram). + +[NOTE] +If any of the aforementioned checks do not succeed, Step 3 would end in a Command Exception being thrown and would not proceed. Model and Storage components would not be modified. + +* Step 4. The `ModelManager` creates a `VersionedCardFolder` to represent the newly created folder, storing a reference to its currently empty list of cards. Before returning control to the Logic component, `ModelManager#indicateModified()` is invoked to notify listeners in the `LogicManager` that the list of `CardFolders` have changed. + +* Step 5. The Logic component takes over control and checks if the `ModelManager` is modified. In the case of `addfolder` the object is indeed modified (as a result of Step 4) and thus the component proceeds to save the Model's `CardFolders` to Storage. + +* Step 6. Before handing over control to Storage, the `LogicManager` obtains the information to save and the path to save to with `ModelManager#getCardFolders()` and `ModelManager#getCardFoldersFilesPath()` respectively. It then passes these objects as parameters when it calls `StorageManager#saveCardFolders()`. + +* Step 7. The Storage component receives control, with the `StorageManager` clearing the directory of data files at the specified path and creating `JsonCardFolderStorage` objects with path names equivalent to the names of the folders it has received. It then proceeds to invoke `JsonCardFolderStorage#saveCardFolder()` on all the `JsonCardFolderStorage` to save all the folders before returning to the `LogicManager`. + +[NOTE] +If the path provided by the Model Component is invalid, the Storage component throws an exception and an error message is displayed. The changes made to Model are not saved and the command does not execute successfully. + +* Step 8. The `LogicManager` terminates and returns the result of the command to the calling method. + +[[addfoldersequencediagram]] +.Component interactions for an `addfolder` command +image::AddFolderSequenceDiagram.png[width="800"] + +===== UI + +As folders are a layer of abstraction over the cards, there is a need for the GUI to represent this abstraction for greater clarity and ease of use for the user. This is done by introducing the `FolderListPanel` class, which displays a list of all folders that the user has. + +The `fullScreenPlaceholder:StackPane` object houses the content in the main window of our application. Depending on whether the user is in the home directory or within a folder, different UI objects are placed within `fullScreenPlaceholder`. + +* When the user is in the home directory, `fullScreenPlaceholder` holds a `FolderListPanel` to display all the folders in a list inside the main window. + +* When the user is within a folder, `fullScreenPlaceholder` holds a `CardMainScreen` object, which is composed of a `CardListPanel` and `BrowserPanel`. These represent the list of cards on the scrolling sidebar, as well as the card viewer on the right. The content within the `CardMainScreen` depends on the particular folder the user has navigated into, as different folders hold different cards. + +To better understand how the UI is updated, below is a walkthrough of what happens when the user enters a folder. Refer to the sequence diagram in <> for a visual representation: + +* Step 1. The Logic component informs the UI component that the user has entered a folder. The UI component responds by invoking `MainWindow#handleEnterFolder()`. + +* Step 2. UI retrieves the list of cards belonging to the entered folder from the `LogicManager`. + +* Step 3. A new `CardListPanel` is created with the information obtained in Step 2. + +* Step 4. The new `CardListPanel` from Step 3, together with the existing `BrowserPanel`, are used to create a new `CardMainScreen` object. + +* Step 5. The content held by `fullScreenPlaceholder` is replaced with the newly generated `CardMainScreen`. + +[[enterfoldersequencediagram]] +.UI behaviour when user enters folder +image::EnterFolderGUISequenceDiagram.png[width="800"] + +==== Design Considerations +===== Aspect: How multiple folders are represented in Model +* **Alternative 1 (current choice)**: List of structures representing individual folders +** Pros: Scalable and better follows OOP principles. +** Cons: Hard to implement, alters fundamental architecture of components. +* **Alternative 2**: A single structure containing `Cards` with information on their folder membership (folder operations performed by iterating over all cards) +** Pros: Easy to implement. +** Cons: Not scalable, will be computationally expensive to perform folder operations when there are many cards and/or folders. + +*Evaluation*: On the account of how computationally expensive it would be to parse through thousands of cards every card or folder operation, the first alternative was chosen and time was set aside to overcome the technical challenge of implementing the choice. + +===== Aspect: Folder identification +* **Alternative 1**: Use a unique folder name +** Pros: Easier to implement. +** Cons: The undo/redo feature would not be compatible with this approach, as checking equality between different versions of a folder across time necessarily requires the comparison of cards. +* **Alternative 2**: Identify a folder by its cards +** Pros: There can be no folders with identical cards, preventing redundancy. +** Cons: Two folders could have identical names as long as the cards are different, which is potentially confusing. +* **Alternative 3 (current choice)**: Mixed approach, use Alternative 1 for comparing different folders and Alternative 2 for comparing the same folder across time +** Pros: Reaps the benefits of both approaches without the disadvantages. +** Cons: Difficult to implement and for future developers to grasp the difference between the two types of comparisons. + +*Evaluation*: While it was difficult the implement, Alternative 3 was chosen due to the immense limitations of the first two approaches. The third alternative had little to no downside apart from requiring time to understand and implement. + +===== Aspect: Storage file name and folder name +* **Alternative 1**: Let folder name be the file name of the storage file +** Pros: Less ambiguity as to how file name is related to folder name, able to find storage file path with folder name. +** Cons: Harder to retrieve folder name from the file as it requires parsing the path, more prone to data corruption as file name could be modified when application is running (although this could be overcome with some OS-level syscalls to lock the file). +* **Alternative 2 (current choice)**: Let file name be independent of folder name, which is stored inside the storage file itself +** Pros: Easier to implement and avoids dependency on existing storage files after application starts. +** Cons: When saving folders from Model, it is difficult to match folders with existing storage files. Hence, rather than saving the modified folder, it is more feasible to clear the directory and save all folders. This is computationally expensive and may not be scalable beyond a certain size. + +*Evaluation*: While in the long-run, the first alternative appears to better decision, time limitations make Alternative 2 good enough for most practical use cases. The saved time was used to implement other features. + +===== Aspect: What folders to generate in the event corrupted storage files are encountered +* **Alternative 1**: Display a sample folder +** Pros: Easy to implement, guaranteed that application will not be empty with no folders displayed. +** Cons: Non-corrupted folders will not be displayed and will potentially be overwritten. +* **Alternative 2**: Display non-corrupted folders +** Pros: Non-corrupted data is preserved. +** Cons: If all data is corrupted, an empty application is presented to the user. +* **Alternative 3 (current choice)**: Mixed approach, display all non-corrupted folders unless all data is corrupted, in which case display sample folder +** Pros: Has the advantages but not the disadvantages of Alternatives 1 and 2. +** Cons: More difficult to implement, developers will need to pay special attention to understand this behaviour. + +*Evaluation*: Alternative 3 was deemed the most logical choice because it achieves the important objectives of retaining non-corrupted user data, while always ensuring there is data for the user to work with even if he/she is starting the application for the first time. + +=== Navigating folders + +==== Current Implementation + +===== Navigation State + +The state of the application with regard to navigation (i.e. whether a user is inside of a folder or at the home directory) affects the types of commands available to the user. + +* The commands that affect cards (e.g. adding a card, editing a card) are executed within folders and are known as *Card Operations*. +* Commands that affect folders (e.g. adding a folder, deleting a folder) are only executable at the home directory and are known as *Folder Operations*. + +Please refer to the User Guide for the full list of commands under both categories. + +To keep track of navigation state, an enum `State` is maintained by the `ModelManager`. Other components may retrieve the current state with `ModelManager#getState()`. This is also how the `Command` objects determines whether the command is executable in the present navigation state. + +===== Change Directory Command + +Folder navigation is achieved by the user through the use of the `cd` command. As navigating folders do not actually modify folders and their cards, folder navigation does not involve the Storage Component. + +The change directory command has the following formats: + +1. `cd ..` - Returns the user to the home directory. This command can only be executed when the user is inside a folder. +2. `cd FOLDER_INDEX` - Enters the folder specified by `FOLDER_INDEX`. This command can only be executed from the home directory, when the user is not in any folder. + +When a `cd` command is executed, the Logic component parses the command and creates a `ChangeDirectoryCommand` object. If the command is of the first format, `ChangeDirectoryCommand()` is invoked without any arguments and the boolean `isExitingFolder` is set to `true`. If the command is of the second format, the overloaded constructor `ChangeDirectoryCommand(FOLDER_INDEX)` is instead called and `isExitingFolder` is set to `false`. + +`ChangeDirectoryCommand#execute()` is then invoked. The value of `isExitingFolder` will determine the corresponding methods in `ModelManager` that are called (`exitFoldersToHome()` or `enterFolder()`). The sequence diagram in <> illustrates this conditional choice and the interactions involved with each option. + +[[changecommandsequencediagram]] +.Component interactions for `cd` command +image::ChangeCommandSequenceDiagram.png[width="600"] + +==== Design Considerations +===== Aspect: Command format to enter and exit folders +* **Alternative 1 (current choice)**: Use variations of the same command (e.g. `cd ..` and `cd INDEX` ) +** Pros: More intuitive and akin to other Command Line applications. +** Cons: Harder to implement as the logic for parsing the command is different from that of existing commands. +* **Alternative 2**: Use distinct commands (e.g. `home` and `enter INDEX`) +** Pros: Commands would function similar to other commands in the application. +** Cons: Harder for the user to get acquainted with as there are two separate commands with logically similar functionality; introduces redundancy. + +*Evaluation*: This matter is highly subjective and conflicting feedback was received during testing. However, we believe that our target audience, a user familiar with the command line, would be more used to navigation with a single command and as such would prefer Alternative 1. +//end::folders[] + +//tag::testcommand[] +=== Test Session feature + +==== Overall Current Implementation +This big feature mainly involves `UI`, `Logic` and `Model` components. + +There are 3 main variables in `ModelManager` introduced to keep track of the current state of the user in a test +session. + +* `currentTestedCardFolder` +** The current card folder that the user is running a test session on (stored as an `ObservableList` of cards) . +** Set to null if user is not inside a test session + +* `currentTestedCard` +** The current card the user is seeing in the test session, obtained from `currentTestedCardFolder` using +`currentTestedCardIndex`. +** Set to null if user is not inside a test session +** Related methods: +*** `ModelManager#setCurrentTestedCard(Card card)` - set `currentTestedCard` to the card specified. +*** `ModelManager#getCurrentTestedCard()` - returns the `currentTestedCard`. + +* `cardAlreadyAnswered` +** A boolean variable to indicate if the user has already executed a valid answer command for the current card. +** Related methods: +*** `ModelManager#setCardAsAnswered()` - set `cardAlreadyAnswered` to true. +*** `ModelManager#setCardAsNotAnswered()` - set `cardAlreadyAnswered` to false. +*** `ModelManager#isCardAlreadyAnswered()` - returns true if the current card has already been answered and false +otherwise. + +==== Overall Design Considerations + +===== Aspect: Usage of an extra card variable to keep track of the current card in test session + +* **Alternative 1 (current choice)**: Introduce another variable, `currentTestedCard`, to store the card to display +in the test session. +** Pros: More reader friendly. Save time from accessing the list to get card at that index. +** Cons: Extra space used. + +* **Alternative 2**: No introduction of `currentTestedCard` as using the `currentTestedCardIndex` suffices. Every time a +card is needed, can simply reference it using `currentTestedCardFolder#getIndex(currentTestedCardIndex)`. +** Pros: No need to store an extra variable so this method uses less space. +** Cons: Not so reader friendly. Need to keep accessing the list using the index which can potentially lead to +possible violation of the Law of Demeter where an object should only interact with objects that are closely related to it. + +*Evaluation*: +We went with Alternative 1 since not a large amount of memory is taken up with just 1 extra card stored. As there will +be several references to the `currentTestedCard`, it will be better to store them somewhere. Abiding by the Law +of Demeter, `currentTestedCard` object will not be interacting with `currentTestedCardFolder`, limiting its +knowledge of that object which is encouraged according to the Principle of Least Knowledge. + + +==== Test / End Command + +===== Current Implementation + +[discrete] +====== Model +The main logic for `test` and `end` command is carried out in `ModelManager` with the following methods: + +* `ModelManager#startTestSession()` - begins a test session on the current card folder user is in and implicitly sorts +the cards in it. +* `ModelManager#endTestSession()` - ends the current test session. + +[discrete] +====== UI +To update the change in the UI to reflect that the user is a test session (app goes to full screen with question of +the current card presented), the following methods are introduced. + +* `MainWindow#handleStartTestSession(Card card)` - creates a new `TestSession` page with the card specified and bring + the page forward in front of the current `CardMainScreen` page. +* `MainWindow#handleEndTestSession()` - deletes the current `TestSession` page and the `CardMainScreen` page at the back + is now presented to the user. + +[discrete] +====== Example Usage of test command + +To illustrate how the `UI`, `Model` and `Logic` components interact, below is a walkthrough of a typical usage scenario of +the test command. +<> is a sequence diagram that summarises `Model` and `Logic` interactions, namely steps 1 to 7. + +**Step 1**. User is inside a folder and wants to begin a test session on the current folder by executing the command +`test`. + +**Step 2**. As with every command, the command parser reads the input and generates the relevant `Command` object, in +this case a `TestCommand`. The object is returned to the `LogicManager`. + +**Step 3**. The `LogicManager` executes the `TestCommand`, storing the result and then transferring control to the +Model component, with `Model#getState()` to check that user is inside a folder and is not already in a test session. +(This is omitted from <> for simplicity.) + +[NOTE] +If the user is not inside a folder, this test command would be rendered invalid. Step 4 would not proceed and an +error message is displayed. + +**Step 4**. After checking user is in a folder, `ModelManager#startTestSession()` method is invoked, which +does the following: + +1. `currentTestedCardFolder` is set to the current folder the user is in, by invoking `getCardList()` from the active +`VersionedCardFolder` in `folders`. +[NOTE] +If this folder is empty such that there is no card to present to the user, an `EmptyCardFolderException` is thrown, +to be caught in `TestCommand`, which then throws a `CommandException` to display an error message. + +2. The cards in this folder are sorted in ascending scores by invoking `sortFilteredCard(comparator)`, so that +lowest score cards will be presented first to the user in a test session. +3. `ModelManager#setCurrentTestedCard(currentTestedCardIndex)` is then invoked to set `currentTestedCard` to the first +card in the folder as `currentTestedCardIndex` is set to 0. +4. `state` in `Model` is set to `IN_TEST` to specify that user is in a test session from now onwards. +5. No change to `cardAlreadyAnswered` as it is by default false. + +**Step 5**. For `TestCommand` to obtain the first card to present in the test session, +`ModelManager#getCurrentTestedCard()` is invoked and the `Card`, `c`, is returned. + +**Step 6**. With control now transferred back to the logic unit, `TestCommand` creates a `CommandResult` object, `r` with the type `START_TEST_SESSION`, and +set `testSessionCard` in `CommandResult` to `c` obtained in Step 5. + +**Step 7**. `r` is returned to `LogicManager` which then terminates and returns `r` to the caller method. + +**Step 8**. The caller method here is in `MainWindow`. Control is now transferred to the UI component. + +**Step 9**. `MainWindow` sees that `CommandResult` object `r` has the type `START_TEST_SESSION`. It invokes +`MainWindow#handleStartTestSession(currentTestedCard)` to display the `currentTestedCard` question and hints to the +user. + +[[testcommandsequencediagram]] +.Component interactions for a test command +image::TestCommandSequenceDiagram.png[width="800"] +//end::testcommand[] + +[[exampleusageofendcommand]] +[discrete] +====== Example Usage of end command + +**Step 1**. User is currently in a test session and executes the command `end`. + +**Step 2**. An `EndCommand` object is created and `LogicManager` executes the `EndCommand`, storing the result and then +transferring control to the Model component, with `Model#getState()` to check that user is indeed in a test session. + +[NOTE] +If the user is not in a test session, Step 3 would not proceed. + +**Step 3**. `ModelManager#endTestSession()` method is invoked, which does the following: + +1. `currentTestedCardFolder` is set to null. +2. `ModelManager#setCurrentTestedCard(null)` is invoked to set `currentTestedCard` to null. +3. `state` in `Model` is set to `IN_FOLDER` to specify that user is back in the folder. +4. `ModelManager#setCardAsNotAnswered()` is invoked. + +**Step 4**. As control is now transferred back to the logic unit, `EndCommand` creates a `CommandResult` object, `r` with +the type `END_TEST_SESSION`. + +**Step 5**. `r` is returned to `LogicManager` which then terminates and returns `r` to the caller in `MainWindow`. Control is now transferred to the UI component. + +**Step 6**. `MainWindow` sees that `CommandResult` object `r` has the type `END_TEST_SESSION`. It invokes +`MainWindow#handleEndTestSession()` to delete the current `testSession` page, presenting `cardMainScreen` page at the +back (the screen the user was seeing before entering the test session) to the user. + +//tag::testdesign[] +===== Design Considerations +====== Aspect: Way to execute a test/end command + +* **Alternative 1 (current choice)**: `test` is executed when inside a folder. The user does not have to specify the +folder index and `test` would just immediately display the first card in this current folder. +** Pros: The most logical way of carrying out a test session is when the user is in the folder that he or she wants to + be tested on. Lesser dependency on entering and exiting folder methods. +** Cons: Requires the extra step of entering the folder before it can test the folder. User may actually see the questions before the test session. + +* **Alternative 2**: `test` is executed when outside a folder, in the home directory. Test command would require a +folder index, e.g `test 1` to test the first folder. Implementation of getting the card from the folder would rely on +enter folder command as well. +** Pros: Fast way to enter test session from home directory +** Cons: Not logical for `test` to be called from inside the home directory which should only allow folder operations. +`test` will then have to implicitly enter the folder to gain access to the cards in it in order to display them, +creating a +dependency between the test and enter folder commands. +Similar issue will arise for the end test session command where user will need to implicitly exit the folder. + +*Evaluation*: +Overall, Alternative 1 is a better choice following the Single Level of Abstraction Principle(SLAP) where a function +should not mix different levels of abstractions. We can then better achieve higher cohesion and lower coupling. +Also, user being able to see the questions before the test session is not a big issue since the answer will not be shown unless user selects the card. + +//end::testdesign[] + +==== Answer Command +===== Current Implementation + +[discrete] +====== Model +To facilitate the marking of attempted answer, we introduce the following method in `ModelManager`. + +* `ModelManager#markAttemptedAnswer(Answer attemptedAnswer)` - returns true if attemptedAnswer is correct and +false if attemptedAnswer is wrong. It compares the attempted answer and the correct answer obtained from the current +card. + +[NOTE] +Comparison is not case-sensitive + +[discrete] +====== Logic +To facilitate the update of score after marking the card, we introduce the following method in `ModelManager`. + +* `ModelManager#createScoredCard(Card cardToMark, boolean markCorrect)` - creates a new card with the +updated score. + +[discrete] +====== UI +To update the change in the `UI` to show the user the result of the marked answer, whether it is correct or wrong, the following methods are introduced. + +* `MainWindow#handleCorrectAnswer()` - updates current `TestSession` page to green colour background with correct +answer and correct answer description. +* `MainWindow#handleWrongAnswer()` - updates current `TestSession` page to red colour background with correct answer +and wrong answer description. + +[discrete] +====== Example Usage of answer command + +To illustrate how the components interact, below is a walkthrough of a typical usage scenario of the answer command. +<> is a sequence diagram that summarises `Model` and `Logic` interactions, namely steps 1 +to 8. + +**Step 1**. User is in a test session and wants to answer the question on the card currently presented by executing +`ans four`. + +**Step 2**. An `AnswerCommand` object is created and `LogicManager` executes the `AnswerCommand`, storing the result and +then transferring control to the Model component, with `Model#getState()` and +`ModelManager#isCardAlreadyAnswered()` to check that user is indeed in a test session and has not attempted an answer + already. (This is omitted from <> for simplicity.) + +[NOTE] +If the user is not in a test session or already attempted an answer for the current card, this `ans` command would be +rendered invalid. Step 3 would not proceed and an error message is displayed. + +**Step 3**. After both checks have passed, `ModelManager#setCardAsAnswered()` is invoked. + +**Step 4**. `ModelManager` now marks the attempted answer by invoking `ModelManager#markAttemptedAnswer(attemptedAnswer)`. + +**Step 5**. Given the result of the attempt, a new card exactly the same as the current +card but with the updated score is created and replaces the current one by invoking `ModelManager#createScoredCard(Card +cardToMark, boolean markCorrect)` followed by `Model#setCard(cardToMark, scoredCard)`. + +**Step 6**. To complete the update in the change in score, `Model#updateFilteredCard(PREDICATE_SHOW_ALL_CARDS)` and +`Model#commitActiveCardFolder()` are invoked. + +**Step 7**. `AnswerCommand` now creates a `CommandResult` object, `r` with either type `ANSWER_CORRECT` or `ANSWER_WRONG` + depending on the outcome of the attempt. + +**Step 8**. `r` is returned to `LogicManager` which then terminates and returns `r` to the caller. + +**Step 9**. The caller method is in `MainWindow`. Control is now transferred to the UI component. + +**Step 10**. If `MainWindow` sees that `CommandResult` object `r` has the type `ANSWER_CORRECT`, it invokes +`MainWindow#handleCorrectAnswer()` to display the correct attempt `TestSession` page to the user. +If `MainWindow` sees that `CommandResult` object `r` has the type `ANSWER_WRONG`, it invokes +`MainWindow#handleWrongAnswer()` to display the wrong attempt `TestSession` page to the user. + +[[answercommandsequencediagram]] +.Component interactions for a answer command +image::AnswerCommandSequenceDiagram.png[width="800"] + +==== Next Command +===== Current Implementation +[discrete] +====== Model +The following method is introduced in `ModelManager` to display to the user the next card in the test session. + +`ModelManager#testNextCard()` - returns true if it successfully finds a next card to present in the test session and +false otherwise (if there is no more cards left to test in the folder). + +[discrete] +====== UI +To display the next card in the test session, the method below is introduced in `MainWindow`. + +* `MainWindow#handleNextCardTestSession(Card card)` - deletes the current `TestSession` page and adds a new +`TestSession` page with this next card specified. + +[discrete] +====== Example Usage of next command + +To illustrate how the `UI`, `Model` and `Logic` components interact, below is a walkthrough of a typical usage scenario of +the next command. +<> is a sequence diagram that summarises `Model` and `Logic` interactions, namely steps 1 +to 6. + +**Step 1**. User is in a test session and wants to move on to the next card by executing a `next`. + +**Step 2**. A `NextCommand` object is created and `LogicManager` executes the `NextCommand`, storing the result and +then transferring control to the Model component, with `Model#getState()` and +`ModelManager#isCardAlreadyAnswered()` to check that user is indeed in a test session and has attempted an +answer already. (This is omitted from <> for simplicity.) + +[NOTE] +If the user is not in a test session or has not attempted an answer for the current card, this `next` command would be +rendered invalid. Step 3 would not proceed and an error message is displayed. + +**Step 3**. After both checks have passed, `ModelManager#testNextCard()` method is invoked, which does the following: + +1. `currentTestedCardIndex` incremented by 1. +2. `currentTestedCardIndex` is then checked if it equals to the size of `currentTestedCardFolder`. +* Case 1: This check returns true. + +This means `currentTestedCardIndex` is invalid and there +is no more next card to be presented to the user. This method immediately returns false. + +* Case 2: This check returns true. + +This means `currentTestedCardIndex` is valid and will be used to get the next card + from `currentTestedCardFolder`. This card is set as the `currentTestedCard` via the `ModelManager#setCurrentTestedCard +(cardToTest)`. `ModelManager#setCardAsNotAnswered` is then invoked to reset the value of `cardAlreadyAnswered`. This +method returns true. + +**Step 4**. From the result of `ModelManager#testNextCard()` method earlier: + +* Case 1: Method returns false. + +A `next` command will be equivalent to an `end` command. `ModelManager#endTestSession()` is invoked. Step 5 does not +proceed. Instead, step 3 and onwards of the <> takes over. + +* Case 2: Method returns true. +For `NextCommand` to obtain the next card to present in the test session, `ModelManager#getCurrentTestedCard()` is +invoked and the `Card`, `c`, is returned. + +**Step 5**. With control now transferred back to the logic unit, `NextCommand` creates a `CommandResult` object, `r` with +the type `SHOW_NEXT_CARD`, and set `testSessionCard` in `CommandResult` to `c`. + +**Step 6**. `r` is returned to `LogicManager` which then terminates and returns `r` to the caller method. + +**Step 7**. The caller method here is in `MainWindow`. Control is now transferred to the UI component. + +**Step 8**. `MainWindow` sees that `CommandResult` object `r` has the type `SHOW_NEXT_CARD`. It invokes +`MainWindow#handleNextCardTestSession(currentTestedCard)` to display this new `currentTestedCard` question and hints +to the user. + +[[nextcommandsequencediagram]] +.Component interactions for a `next` command +image::NextCommandSequenceDiagram.png[width="800"] + +===== Design Considerations +====== Aspect: Behavior of next command executed on the last card + +* **Alternative 1 (current choice)**: A `next` command will be equivalent to an `end` command +** Pros: More convenient and user-friendly. It is common sense to end the test session for the user. +** Cons: By right, it is not correct since next command is just to show the next card. + +* **Alternative 2**: A `next` command will throw an exception +** Pros: Most correct way to do it since there is no next card to display. +** Cons: User may not understand. It is not user-friendly as user has to keep track of which card it is at to prevent the exception thrown. + +*Evaluation*: +With our target audience in mind, Alternative 1 is the more user friendly and intuitive way to handle this scenario. + + + // tag::undoredo[] === Undo/Redo feature + +[NOTE] +The following section details a feature implemented in the earlier iteration of the application. As such, the diagrams still refer to `AddressBook`, which has since replaced with `CardFolder`. The outdated diagrams here will be updated by `v2.0`. + ==== Current Implementation -The undo/redo mechanism is facilitated by `VersionedAddressBook`. -It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. +The undo/redo mechanism is facilitated by `VersionedCardFolder`. +It extends `CardFolder` with an undo/redo history, stored internally as an `cardFolderStateList` and `currentStatePointer`. Additionally, it implements the following operations: -* `VersionedAddressBook#commit()` -- Saves the current address book state in its history. -* `VersionedAddressBook#undo()` -- Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` -- Restores a previously undone address book state from its history. +* `VersionedCardFolder#commit()` -- Saves the current card folder state in its history. +* `VersionedCardFolder#undo()` -- Restores the previous card folder state from its history. +* `VersionedCardFolder#redo()` -- Restores a previously undone card folder state from its history. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +These operations are exposed in the `Model` interface as `Model#commitCardFolder()`, `Model#undoCardFolder()` and `Model#redoCardFolder()` respectively. Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +Step 1. The user launches the application for the first time. The `VersionedCardFolder` will be initialized with the initial card folder state, and the `currentStatePointer` pointing to that single card folder state. image::UndoRedoStartingStateListDiagram.png[width="800"] -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +Step 2. The user executes `delete 5` command to delete the 5th card in the card folder. The `delete` command calls `Model#commitCardFolder()`, causing the modified state of the card folder after the `delete 5` command executes to be saved in the `cardFolderStateList`, and the `currentStatePointer` is shifted to the newly inserted card folder state. image::UndoRedoNewCommand1StateListDiagram.png[width="800"] -Step 3. The user executes `add n/David ...` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +Step 3. The user executes `add q/Some question ...` to add a new card. The `add` command also calls `Model#commitCardFolder()`, causing another modified card folder state to be saved into the `cardFolderStateList`. image::UndoRedoNewCommand2StateListDiagram.png[width="800"] [NOTE] -If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +If a command fails its execution, it will not call `Model#commitCardFolder()`, so the card folder state will not be saved into the `cardFolderStateList`. -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +Step 4. The user now decides that adding the card was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoCardFolder()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous card folder state, and restores the card folder to that state. image::UndoRedoExecuteUndoStateListDiagram.png[width="800"] [NOTE] -If the `currentStatePointer` is at index 0, pointing to the initial address book state, then there are no previous address book states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo. +If the `currentStatePointer` is at index 0, pointing to the initial card folder state, then there are no previous card folder states to restore. The `undo` command uses `Model#canUndoCardFolder()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo. The following sequence diagram shows how the undo operation works: image::UndoRedoSequenceDiagram.png[width="800"] -The `redo` command does the opposite -- it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +The `redo` command does the opposite -- it calls `Model#redoCardFolder()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the card folder to that state. [NOTE] -If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone address book states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. +If the `currentStatePointer` is at index `cardFolderStateList.size() - 1`, pointing to the latest card folder state, then there are no undone card folder states to restore. The `redo` command uses `Model#canRedoCardFolder()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +Step 5. The user then decides to execute the command `list`. Commands that do not modify the card folder, such as `list`, will usually not call `Model#commitCardFolder()`, `Model#undoCardFolder()` or `Model#redoCardFolder()`. Thus, the `cardFolderStateList` remains unchanged. image::UndoRedoNewCommand3StateListDiagram.png[width="800"] -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. We designed it this way because it no longer makes sense to redo the `add n/David ...` command. This is the behavior that most modern desktop applications follow. +Step 6. The user executes `clear`, which calls `Model#commitCardFolder()`. Since the `currentStatePointer` is not pointing at the end of the `cardFolderStateList`, all card folder states after the `currentStatePointer` will be purged. We designed it this way because it no longer makes sense to redo the `add q/Some question ...` command. This is the behavior that most modern desktop applications follow. image::UndoRedoNewCommand4StateListDiagram.png[width="800"] @@ -296,30 +1178,23 @@ image::UndoRedoActivityDiagram.png[width="650"] ===== Aspect: How undo & redo executes -* **Alternative 1 (current choice):** Saves the entire address book. +* **Alternative 1 (current choice):** Saves the entire card folder. ** Pros: Easy to implement. ** Cons: May have performance issues in terms of memory usage. * **Alternative 2:** Individual command knows how to undo/redo by itself. -** Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). +** Pros: Will use less memory (e.g. for `delete`, just save the card being deleted). ** Cons: We must ensure that the implementation of each individual command are correct. ===== Aspect: Data structure to support the undo/redo commands -* **Alternative 1 (current choice):** Use a list to store the history of address book states. +* **Alternative 1 (current choice):** Use a list to store the history of card folder states. ** Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project. -** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedAddressBook`. +** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedCardFolder`. * **Alternative 2:** Use `HistoryManager` for undo/redo ** Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase. ** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two different things. // end::undoredo[] -// tag::dataencryption[] -=== [Proposed] Data Encryption - -_{Explain here how the data encryption feature will be implemented}_ - -// end::dataencryption[] - === Logging We are using `java.util.logging` package for logging. The `LogsCenter` class is used to manage the logging levels and logging destinations. @@ -373,7 +1248,7 @@ image::chrome_save_as_pdf.png[width="300"] [[Docs-SiteWideDocSettings]] === Site-wide Documentation Settings -The link:{repoURL}/build.gradle[`build.gradle`] file specifies some project-specific https://asciidoctor.org/docs/user-manual/#attributes[asciidoc attributes] which affects how all documentation files within this project are rendered. +The link: https://github.com/cs2103-ay1819s2-w10-4/main/blob/master/build.gradle[`build.gradle`] file specifies some project-specific https://asciidoctor.org/docs/user-manual/#attributes[asciidoc attributes] which affects how all documentation files within this project are rendered. [TIP] Attributes left unset in the `build.gradle` file will use their *default value*, if any. @@ -381,11 +1256,11 @@ Attributes left unset in the `build.gradle` file will use their *default value*, [cols="1,2a,1", options="header"] .List of site-wide attributes |=== -|Attribute name |Description |Default value +|Attribute question |Description |Default value |`site-name` -|The name of the website. -If set, the name will be displayed near the top of the page. +|The question of the website. +If set, the question will be displayed near the top of the page. |_not set_ |`site-githuburl` @@ -413,7 +1288,7 @@ Attributes left unset in `.adoc` files will use their *default value*, if any. [cols="1,2a,1", options="header"] .List of per-file attributes, excluding Asciidoctor's built-in attributes |=== -|Attribute name |Description |Default value +|Attribute question |Description |Default value |`site-section` |Site section that the document belongs to. @@ -431,15 +1306,16 @@ _{asterisk} Official SE-EDU projects only_ === Site Template -The files in link:{repoURL}/docs/stylesheets[`docs/stylesheets`] are the https://developer.mozilla.org/en-US/docs/Web/CSS[CSS stylesheets] of the site. +The files in link: https://github.com/cs2103-ay1819s2-w10-4/main/tree/master/docs/stylesheets[`docs/stylesheets`] are + the https://developer.mozilla.org/en-US/docs/Web/CSS[CSS stylesheets] of the site. You can modify them to change some properties of the site's design. -The files in link:{repoURL}/docs/templates[`docs/templates`] controls the rendering of `.adoc` files into HTML5. +The files in link: https://github.com/cs2103-ay1819s2-w10-4/main/tree/master/docs/templates[`docs/templates`] controls the rendering of `.adoc` files into HTML5. These template files are written in a mixture of https://www.ruby-lang.org[Ruby] and http://slim-lang.com[Slim]. [WARNING] ==== -Modifying the template files in link:{repoURL}/docs/templates[`docs/templates`] requires some knowledge and experience with Ruby and Asciidoctor's API. +Modifying the template files in link: https://github.com/cs2103-ay1819s2-w10-4/main/tree/master/docs/templates[`docs/templates`] requires some knowledge and experience with Ruby and Asciidoctor's API. You should only modify them if you need greater control over the site's layout than what stylesheets can provide. The SE-EDU team does not provide support for modified template files. ==== @@ -478,14 +1354,14 @@ We have two types of tests: . *GUI Tests* - These are tests involving the GUI. They include, .. _System Tests_ that test the entire App by simulating user actions on the GUI. These are in the `systemtests` package. -.. _Unit tests_ that test the individual components. These are in `seedu.address.ui` package. +.. _Unit tests_ that test the individual components. These are in `seedu.knowitall.ui` package. . *Non-GUI Tests* - These are tests not involving the GUI. They include, .. _Unit tests_ targeting the lowest level methods/classes. + -e.g. `seedu.address.commons.StringUtilTest` +e.g. `seedu.knowitall.commons.StringUtilTest` .. _Integration tests_ that are checking the integration of multiple code units (those code units are assumed to be working). + -e.g. `seedu.address.storage.StorageManagerTest` +e.g. `seedu.knowitall.storage.StorageManagerTest` .. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together. + -e.g. `seedu.address.logic.LogicManagerTest` +e.g. `seedu.knowitall.logic.LogicManagerTest` === Troubleshooting Testing @@ -515,400 +1391,312 @@ When a pull request has changes to asciidoc files, you can use https://www.netli Here are the steps to create a new release. -. Update the version number in link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp.java`]. +. Update the version number in link: https://github.com/cs2103-ay1819s2-w10-4/main/blob/master/src/main/java/seedu/knowitall/MainApp.java[`MainApp.java`]. . Generate a JAR file <>. . Tag the repo with the version number. e.g. `v0.1` . https://help.github.com/articles/creating-releases/[Create a new release using GitHub] and upload the JAR file you created. === Managing Dependencies -A project often depends on third-party libraries. For example, Address Book depends on the https://github.com/FasterXML/jackson[Jackson library] for JSON parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives: +A project often depends on third-party libraries. For example, card folder depends on the https://github.com/FasterXML/jackson[Jackson library] for JSON parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives: [loweralpha] . Include those libraries in the repo (this bloats the repo size) . Require developers to download those libraries manually (this creates extra work for developers) -[[GetStartedProgramming]] [appendix] -== Suggested Programming Tasks to Get Started - -Suggested path for new programmers: - -1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in <>. - -2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. <> explains how to go about adding such a feature. - -[[GetStartedProgramming-EachComponent]] -=== Improving each component - -Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work). - -[discrete] -==== `Logic` component - -*Scenario:* You are in charge of `logic`. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases. - -[TIP] -Do take a look at <> before attempting to modify the `Logic` component. - -. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing `clear`, the user can also type `c` to remove all persons in the list. -+ -**** -* Hints -** Just like we store each individual command word constant `COMMAND_WORD` inside `*Command.java` (e.g. link:{repoURL}/src/main/java/seedu/address/logic/commands/FindCommand.java[`FindCommand#COMMAND_WORD`], link:{repoURL}/src/main/java/seedu/address/logic/commands/DeleteCommand.java[`DeleteCommand#COMMAND_WORD`]), you need a new constant for aliases as well (e.g. `FindCommand#COMMAND_ALIAS`). -** link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] is responsible for analyzing command words. -* Solution -** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute the same intended command. -** Add new tests for each of the aliases that you have added. -** Update the user guide to document the new aliases. -** See this https://github.com/se-edu/addressbook-level4/pull/785[PR] for the full solution. -**** - -[discrete] -==== `Model` component - -*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. - -[TIP] -Do take a look at <> before attempting to modify the `Model` component. - -. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the address book. -+ -**** -* Hints -** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] API need to be updated. -** Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags? -** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] allows you to update a person, and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] allows you to update the tags. -* Solution -** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. Loop through each person, and remove the `tag` from each person. -** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`] should call `AddressBook#removeTag(Tag)`. -** Add new tests for each of the new public methods that you have added. -** See this https://github.com/se-edu/addressbook-level4/pull/790[PR] for the full solution. -**** - -[discrete] -==== `Ui` component - -*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your address book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn't prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems. - -[TIP] -Do take a look at <> before attempting to modify the `UI` component. - -. Use different colors for different tags inside person cards. For example, `friends` tags can be all in brown, and `colleagues` tags can be all in yellow. -+ -**Before** -+ -image::getting-started-ui-tag-before.png[width="300"] -+ -**After** -+ -image::getting-started-ui-tag-after.png[width="300"] -+ -**** -* Hints -** The tag labels are created inside link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[the `PersonCard` constructor] (`new Label(tag.tagName)`). https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Label.html[JavaFX's `Label` class] allows you to modify the style of each Label, such as changing its color. -** Use the .css attribute `-fx-background-color` to add a color. -** You may wish to modify link:{repoURL}/src/main/resources/view/DarkTheme.css[`DarkTheme.css`] to include some pre-defined colors using css, especially if you have experience with web-based css. -* Solution -** You can modify the existing test methods for `PersonCard` 's to include testing the tag's color as well. -** See this https://github.com/se-edu/addressbook-level4/pull/798[PR] for the full solution. -*** The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes. -**** - -. Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] such that link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] can show a different style on error (currently it shows the same regardless of errors). -+ -**Before** -+ -image::getting-started-ui-result-before.png[width="200"] -+ -**After** -+ -image::getting-started-ui-result-after.png[width="200"] -+ -**** -* Hints -** link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] is raised by link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] which also knows whether the result is a success or failure, and is caught by link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] which is where we want to change the style to. -** Refer to link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] for an example on how to display an error. -* Solution -** Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] 's constructor so that users of the event can indicate whether an error has occurred. -** Modify link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)`] to react to this event appropriately. -** You can write two different kinds of tests to ensure that the functionality works: -*** The unit tests for `ResultDisplay` can be modified to include verification of the color. -*** The system tests link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest#assertCommandBoxShowsDefaultStyle() and AddressBookSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. -** See this https://github.com/se-edu/addressbook-level4/pull/799[PR] for the full solution. -*** Do read the commits one at a time if you feel overwhelmed. -**** - -. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the address book. -+ -**Before** -+ -image::getting-started-ui-status-before.png[width="500"] -+ -**After** -+ -image::getting-started-ui-status-after.png[width="500"] -+ -**** -* Hints -** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! -** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated. -* Solution -** Modify the constructor of link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of persons when the application just started. -** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleAddressBookChangedEvent(AddressBookChangedEvent)`] to update the number of persons whenever there are new changes to the addressbook. -** For tests, modify link:{repoURL}/src/test/java/guitests/guihandles/StatusBarFooterHandle.java[`StatusBarFooterHandle`] by adding a state-saving functionality for the total number of people status, just like what we did for save location and sync status. -** For system tests, modify link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest`] to also verify the new total number of persons status bar. -** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. -**** - -[discrete] -==== `Storage` component - -*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage. - -[TIP] -Do take a look at <> before attempting to modify the `Storage` component. - -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. -+ -**** -* Hint -** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/AddressBookStorage.java[`AddressBookStorage`] interface. -** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/JsonAddressBookStorage.java[`JsonAddressBookStorage`] class. -* Solution -** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. -**** - -[[GetStartedProgramming-RemarkCommand]] -=== Creating a new command: `remark` - -By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app. - -*Scenario:* You are a software maintainer for `addressbook`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. - -==== Description -Edits the remark for a person specified in the `INDEX`. + -Format: `remark INDEX r/[REMARK]` - -Examples: +== Product Scope -* `remark 1 r/Likes to drink coffee.` + -Edits the remark for the first person to `Likes to drink coffee.` -* `remark 1 r/` + -Removes the remark for the first person. +*Target user profile*: -==== Step-by-step Instructions +* medicine students who need to rote memorisation of information +* finds carrying physical flashcards around troublesome and prefers an application to help them store and organize +their learning material +* prefer desktop apps over other types +* can type fast +* prefers typing over mouse input +* is reasonably comfortable using CLI apps -===== [Step 1] Logic: Teach the app to accept 'remark' which does nothing -Let's start by teaching the application how to parse a `remark` command. We will add the logic of `remark` later. +*Value proposition*: flashcards that are able to test the user instead of simply having them recall the answer. The +user experience is more engaging and scoring is more accurate as it is based on actual performance rather than reported performance. -**Main:** +[appendix] +== User Stories -. Add a `RemarkCommand` that extends link:{repoURL}/src/main/java/seedu/address/logic/commands/Command.java[`Command`]. Upon execution, it should just throw an `Exception`. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to accept a `RemarkCommand`. +Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` -**Tests:** +[width="59%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= +|Priority |As a ... |I want to ... |So that I can... -. Add `RemarkCommandTest` that tests that `execute()` throws an Exception. -. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. +|`* * *`|student|have flashcards with questions and answers|have an easier time memorising content -===== [Step 2] Logic: Teach the app to accept 'remark' arguments -Let's teach the application to parse arguments that our `remark` command will accept. E.g. `1 r/Likes to drink coffee.` +|`* * *`|student|create and delete my own flashcards| -**Main:** +|`* * *`|student|edit the content of my flashcards|add on more content or correct any errors -. Modify `RemarkCommand` to take in an `Index` and `String` and print those two parameters as the error message. -. Add `RemarkCommandParser` that knows how to parse two arguments, one index and one with prefix 'r/'. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to use the newly implemented `RemarkCommandParser`. +|`* * *`|student|have folders to store flashcards|logically group flashcards of the same topic -**Tests:** +|`* * *`|student|navigate in and out of folders|see one folder's cards at each point of time -. Modify `RemarkCommandTest` to test the `RemarkCommand#equals()` method. -. Add `RemarkCommandParserTest` that tests different boundary values -for `RemarkCommandParser`. -. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`] to test that the correct command is generated according to the user input. +|`* * *` |student|test myself on each flashcard folder|better learn the content -===== [Step 3] Ui: Add a placeholder for remark in `PersonCard` -Let's add a placeholder on all our link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] s to display a remark for each person later. +|`* * *`|student|attempt keying in answers before flashcards reveal them|have a more engaging experience -**Main:** +|`* * *`|student|view the answers of questions directly|proceed even when I do not remember the answer -. Add a `Label` with any random text inside link:{repoURL}/src/main/resources/view/PersonListCard.fxml[`PersonListCard.fxml`]. -. Add FXML annotation in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] to tie the variable to the actual label. +|`* *`|student|know how well I've been performing on each flashcard|know my overall progress -**Tests:** +|`* *`|student|view a progress report by folder|know my performance for each topic -. Modify link:{repoURL}/src/test/java/guitests/guihandles/PersonCardHandle.java[`PersonCardHandle`] so that future tests can read the contents of the remark label. +|`* *` |student|sort flashcards by score |know which questions i have more trouble answering -===== [Step 4] Model: Add `Remark` class -We have to properly encapsulate the remark in our link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] class. Instead of just using a `String`, let's follow the conventional class structure that the codebase already uses by adding a `Remark` class. +|`* *`|student|import and export flashcards|share content -**Main:** +|`* *`|student|search flash cards in a folder|save time looking for a particular card -. Add `Remark` to model component (you can copy from link:{repoURL}/src/main/java/seedu/address/model/person/Address.java[`Address`], remove the regex and change the names accordingly). -. Modify `RemarkCommand` to now take in a `Remark` instead of a `String`. +|`* *`|student|search folders|save time looking for a particular folder -**Tests:** +|`* *`|student|move flashcards from one folder to another|better manage my flashcards -. Add test for `Remark`, to test the `Remark#equals()` method. +|`*` |student|add hints that I can toggle on/off|get help with more difficult cards -===== [Step 5] Model: Modify `Person` to support a `Remark` field -Now we have the `Remark` class, we need to actually use it inside link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +|`*`|student|add pictures to certain flashcards|better represent topics that heavily feature topics and diagrams -**Main:** +|`*`|student|have a question that expects more than one answer|test myself more complex questions -. Add `getRemark()` in link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. -. You may assume that the user will not be able to use the `add` and `edit` commands to modify the remarks field (i.e. the person will be created without a remark). -. Modify link:{repoURL}/src/main/java/seedu/address/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks for the sample data (delete your `data/addressbook.json` so that the application will load the sample data when you launch it.) +|`*` |student|different template designs for my flashcards|have a personalised experience while revising +|======================================================================= -===== [Step 6] Storage: Add `Remark` field to `JsonAdaptedPerson` class -We now have `Remark` s for `Person` s, but they will be gone when we exit the application. Let's modify link:{repoURL}/src/main/java/seedu/address/storage/JsonAdaptedPerson.java[`JsonAdaptedPerson`] to include a `Remark` field so that it will be saved. +[appendix] +== Use Cases -**Main:** +(For all use cases below, the *System* is `Know-It-All` and the *Actor* is the `Student`, unless specified otherwise) -. Add a new JSON field for `Remark`. +//tag::testsessionusecases[] +[discrete] +=== UC01 Test flashcards -**Tests:** +*MSS* -. Fix `invalidAndValidPersonAddressBook.json`, `typicalPersonsAddressBook.json`, `validAddressBook.json` etc., such that the JSON tests will not fail due to a missing `remark` field. +1. Student is inside a folder and begins a test session. +2. System presents the question on the lowest-performing flashcard first. +3. Student inputs his/her answer. +4. System indicates whether student’s answer is correct or wrong and shows the answer of the flashcard. +5. Student navigates to next flashcard. +6. Repeat steps 2-4 until all the flashcards in the folder are tested. ++ +Use case ends -===== [Step 6b] Test: Add withRemark() for `PersonBuilder` -Since `Person` can now have a `Remark`, we should add a helper method to link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`], so that users are able to create remarks when building a link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +*Extensions* -**Tests:** +[none] +* 3a. Student doesn't know the answer and wants to see the answer without attempting. +[none] +** 3a1. Student uses the reveal command. +** 3a2. Answer is displayed to the student. -. Add a new method `withRemark()` for link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`]. This method will create a new `Remark` for the person that it is currently building. -. Try and use the method on any sample `Person` in link:{repoURL}/src/test/java/seedu/address/testutil/TypicalPersons.java[`TypicalPersons`]. +//end::testsessionusecases[] -===== [Step 7] Ui: Connect `Remark` field to `PersonCard` -Our remark label in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] is still a placeholder. Let's bring it to life by binding it with the actual `remark` field. +//tag::cardusecases[] +[discrete] +=== UC02 Add flashcards -**Main:** +*MSS* -. Modify link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`]'s constructor to bind the `Remark` field to the `Person` 's remark. +1. Student navigates to a folder that he wants to add a flashcard to. +2. Student inputs question and answer to be stored as flashcard. +3. System stores the details as a flashcard under the current folder. ++ +Use case ends -**Tests:** +*Extensions* -. Modify link:{repoURL}/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysPerson(...)`] so that it will compare the now-functioning remark label. +[none] +* 2a. Student only inputs a question but no answer. +[none] +** 2a1. System displays an error message informing the user that the command format is invalid. ++ +Use case resumes from step 2. -===== [Step 8] Logic: Implement `RemarkCommand#execute()` logic -We now have everything set up... but we still can't modify the remarks. Let's finish it up by adding in actual logic for our `remark` command. +[discrete] +=== UC03 Edit flashcard question -**Main:** +*MSS* -. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a person. +1. Student navigates to the folder that contains the flashcard to be edited. +2. Student indicates the card to be edited, as well as the new question. +3. System stores the updated details for the edited card. ++ +Use case ends -**Tests:** +*Extensions* -. Update `RemarkCommandTest` to test that the `execute()` logic works. +[none] +* 2a. Student enters a blank as the desired question. +[none] +** 2a1. System displays an error message informing the user that the question cannot be a blank. ++ +Use case resumes from step 2. -==== Full Solution +[none] +* 2b. Student enters a card index that does not exist. +[none] +** 2b1. System displays an error message prompting the user to choose a valid card index. ++ +Use case resumes from step 2. +//end::cardusecases[] -See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step-by-step solution. +//tag::folderusecases[] +[discrete] +=== UC04 Add folder -[appendix] -== Product Scope +*Guarantees* -*Target user profile*: +* A folder of the desired name is created. -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing over mouse input -* is reasonably comfortable using CLI apps +*MSS* -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +1. Student navigates to home directory. +2. Student inputs the name of the folder he wants to create. +3. System creates a folder of the desired name and shows it on the home directory. ++ +Use case ends -[appendix] -== User Stories +*Extensions* -Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` +[none] +* 2a. Student inputs a name that already exists. +[none] +** 2a1. System displays an error message prompting the user to use a folder name that is not taken. ++ +Use case resumes from step 2. -[width="59%",cols="22%,<23%,<25%,<30%",options="header",] -|======================================================================= -|Priority |As a ... |I want to ... |So that I can... -|`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App +[discrete] +=== UC05 Edit folder name -|`* * *` |user |add a new person | +*Guarantees* -|`* * *` |user |delete a person |remove entries that I no longer need +* A particular folder as selected by the student is renamed to the desired name. -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +*MSS* -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +1. Student navigates to home directory. +2. Student indicates the folder he wants to rename, as well as the new name. +3. System renames the folder to the new name and shows it on the home directory. ++ +Use case ends -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +*Extensions* -_{More to be added}_ +[none] +* 2a. Student inputs a name that already exists. +[none] +** 2a1. System displays an error message prompting the user to use a folder name that is not taken. ++ +Use case resumes from step 2. -[appendix] -== Use Cases +[none] +* 2b. Student chooses a folder that does not exist. +[none] +** 2b1. System displays an error message prompting the user to choose a valid folder. ++ +Use case resumes from step 2. -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +[none] +* 2c. Student enters a blank as the desired new folder name. +[none] +** 2c1. System displays an error message informing that the folder name cannot be a blank. ++ +Use case resumes from step 2. [discrete] -=== Use case: Delete person +=== UC06 Navigating into folders *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. Student indicates the folder he wants to enter. +2. System enters the folder and displays the folder content. + -Use case ends. +Use case ends *Extensions* [none] -* 2a. The list is empty. +* 1a. Student chooses a folder that does not exist. +[none] +** 1a1. System displays an error message prompting the user to choose a valid folder. + -Use case ends. +Use case resumes from step 1. -* 3a. The given index is invalid. -+ [none] -** 3a1. AddressBook shows an error message. +* 1b. Student is already inside a folder. +[none] +** 1b1. System displays an error message informing that the user can only navigate into the folder when he is at the home directory. +** 1b2. Student navigates back to home directory. ++ +Use case resumes from step 1. +//end::folderusecases[] + +//tag::reportusecase[] +[discrete] +=== UC06 Display report for a folder + +*MSS* + +1. Student enters the a folder. +2. Student indicates that they want to see the report for this folder. +3. System displays a full-screen report. + -Use case resumes at step 2. +Use case ends + +//end::reportusecase[] -_{More to be added}_ [appendix] == Non Functional Requirements . Should work on any <> as long as it has Java `9` or higher installed. -. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +. Should be able to hold up to 1000 cards without a noticeable sluggishness in performance for typical usage. . A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. +. The user interface should be intuitive enough even for medical students to use the app. +. In the event where corrupted files are present, uncorrupted files should be preserved so that users do not lose +their data. +. When no data is present, sample data should be generated so users are able to use the app on launch. +. Know-It-All works without active internet connection. +. Know-It-All needs to be able to allow at least 50 characters for folder names and 256 characters for card +information. -_{More to be added}_ [appendix] +// tag::glossary[] == Glossary -[[mainstream-os]] Mainstream OS:: -Windows, Linux, Unix, OS-X +[[cardanswer]] **Card Answer**: The correct answer of a card. -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others +[[cardhint]] **Card Hint**: The optional hint of a card. -[appendix] -== Product Survey +[[cardoption]] **Card Option**: An incorrect option for an MCQ card. + +[[cardquestion]] **Card Question**: The question of a card. -*Product Name* +[[cardscore]] **Card Score**: The number of correct answers divided by the number of attempts for a single card. When the user is tested on a card, this number is automatically calculated and recorded. -Author: ... +[[flashcard]]**Flashcard/Card**: An object containing a single question and answer, and optionally, hints. There are 2 types of cards, Single-answer and MCQ. MCQ cards feature incorrect options in addition to the card answer, while Single-answer cards do not. -Pros: +[[folder]] **Folder**: A collections of flashcards, grouped topically. There are no +sub-folders. -* ... -* ... +[[testscore]] **Test score**: The number of cards correctly answered over number of cards attempted during a test session. This number is automatically recorded after each test session. -Cons: +[[homedirectory]] **Home Directory**: The home page where all the folders are listed. From here, users can enter folders to view cards. -* ... -* ... +[[mainstream-os]]**Mainstream OS**: Windows, Linux, Unix, OS-X + +[[testsession]] **Test Session**: A session where all flashcards in a folder are queued to have their +questions displayed. The user is required to key in an answer for each question. + +// end::glossary[] [appendix] == Instructions for Manual Testing @@ -924,7 +1712,7 @@ These instructions only provide a starting point for testers to work on; testers .. Download the jar file and copy into an empty folder .. Double-click the jar file + - Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + Expected: Shows the GUI with a sample folder. The window size may not be optimal. . Saving window preferences @@ -932,26 +1720,268 @@ These instructions only provide a starting point for testers to work on; testers .. Re-launch the app by double-clicking the jar file. + Expected: The most recent window size and location is retained. -_{ more test cases ... }_ +// tag::cardmanualtesting[] + +=== Card operations +. Adding a single-answer card +.. Prerequisites: Enter a folder using the `cd` command. For example, `cd 1` will enter the first folder. The folder may or may not be empty. +.. Test case: `add q/What is the largest organ of the human body? a/Skin h/Anatomy` + + Expected: A new card with question "What is the largest organ of the human body?", answer "Skin" and hint "Anatomy" is created. Status message confirms this and the card list panel UI on the left updates to show the new card. +.. Test case: `add a/Alexander Fleming q/Who discovered Penicillin?` + + Expected: A new card with question "Who discovered Penicillin?" and answer "Alexander Fleming" is created. Note that this card has no hint, as none was specified. Status message confirms this and UI is updated. +.. Test case: `add q/ a/Some answer` + + Expected: The card is not created. Error details shown in the status message. +.. Other incorrect `add` commands to try for single-answer cards: + + `add`, `add some question`, `add q/ a/ h/`, `add q/Some question a/`. + + Expected: Similar to previous. -=== Deleting a person +. Adding an MCQ card +.. Prerequisites: Enter a folder using the `cd` command. +.. Test case: `add q/What does MRI stand for? a/Magnetic Resonance Imaging i/Medical Resource Image` + + Expected: A new card with question "What does MRI stand for?", answer "Magnetic Resonance Imaging" and incorrect option "Medical Resource Image" is created. Status message confirms this and UI is updated. +.. Test case: (After executing step 1c) `add a/Alexander Fleming q/Who discovered Penicillin? i/Hippocrates` + + Expected: The card is not created. Error details shown in the status message. +.. Other incorrect `add` commands to try for MCQ cards: + + `add q/ a/ i/ i/`, `add q/Some question a/Some answer, i/`, `add q/question a/answer i/answer` + + Expected: Similar to previous. -. Deleting a person while all persons are listed +. Editing a card +.. Prerequisites: Enter a folder using the `cd` command. Ensure that cards already exist in the folder. For example, `select 1` should execute and display a card in right-side panel. +.. Test case: `edit 1 a/Sigmund Freud h/Psychology` + + Expected: First card in the folder is edited to have answer "Sigmund Freud" and hint "Psychology". +.. Test case: `edit 1 h/` + + Expected: First card in the folder is edited to have no hint. +.. Test case: `edit 1 i/Hippocrates i/Carl Jung` + + Expected: First card in the folder is edited to have incorrect options "Hippocrates", "Carl Jung", and is converted to an MCQ card if it was originally a single-answer card. +.. Test case: `edit 1 i/` + + Expected: First card in the folder is edited to have no incorrect options, and is converted back to a single-answer card. +.. Test case: `edit 1 q/` + + Expected: The card is not edited. Error details shown in the status message. +.. Other incorrect `edit` commands to try: `edit x`, `edit`, `edit 1 q/ a/` + + Expected: Similar to previous. -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +. Deleting a card +.. Prerequisites: Enter a folder using the `cd` command. Ensure that cards already exist in the folder. .. Test case: `delete 1` + - Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. -.. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. -.. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + + Expected: First card in the folder is deleted. +.. Test case: `delete` + + Expected: No card is deleted. Error details shown in the status message. +.. Other incorrect `delete` commands to try: `delete 0`, `delete this` + Expected: Similar to previous. -_{ more test cases ... }_ +// end::cardmanualtesting[] + +// tag::testmanualtesting[] +=== Starting and ending a test session + +. Start a test session while inside a folder + +.. Prerequisites: Enter a folder using the `cd` command. For example, `cd 1` will enter the first folder. All the cards + in the folder are listed. +.. Test case: `test` + + Expected: Enters a test session, where the display area enters a fullscreen and the lowest scoring + flashcard question and hints (if any) will be displayed first. `In Test Session` is shown on the status message + and status bar. +.. Test case: `test` on an empty folder (this folder has no cards) + + Expected: The error `This command is not valid on an empty folder` is shown in the status message. Status bar + remains the same (still in folder). +.. Test case: `test` when not inside a folder (prerequisite not fulfilled, like being in the home directory, already +in a test session or in report display) + + Expected: The error `Command can only be executed in folder` is shown in the status message. Status bar + remains the same. + +. Ends a current test session + +.. Prerequisites: Enter a test session by typing `test` when inside a folder. Display area enters fullscreen and +lowest scoring flashcard question and hints are displayed. Tester can also be in any state in the test session +.. Test case: `end` + + Expected: Ends the current test session, where the display area exits fullscreen and be back inside the folder. `End + Test Session` is shown on the status message and status bar shows that tester is back inside the folder. +.. Test case: `end` when not inside a test or report (prerequisite not fulfilled, like being in the home directory or + in a folder) + + Expected: The error `This command is not valid outside a test or report` is shown in the status message. Status bar + remains the same. + +// end::testmanualtesting[] + +[[answerquestionintest]] +=== Answering a question in a test session + +. Input an answer to a question inside a test session + +.. Prerequisites: Enter a test session by typing `test` when inside a folder. Display area enters fullscreen and +lowest scoring flashcard question and hints are displayed +.. Test case: `ans x` where x can be anything but empty for **non-MCQ cards** + + Expected: If the correct answer to the flashcard question is `x`, a green page with correct + attempt notification will be displayed. Otherwise, a red page with wrong answer notification will be displayed. + For both cases, status bar remains the same to be still in test session +.. Test case: `ans x` where x can be any of the option number displayed for **MCQ cards** + + Expected: If the option number with the correct answer to the flashcard question is `x`, a green + page with correct attempt notification will be displayed. Otherwise, a red page with wrong answer notification + will be displayed. For both cases, status bar remains the same to be still in test session +.. Test case: `ans x` where x is not an option number and not empty for **MCQ cards** + + Expected: The error `MCQ question expects option number as answer` is shown in the status message. Status bar remains the same. +.. Test case: `ans` + + Expected: The error `Invalid command format` is shown in the status message. Status bar remains the same. +.. Test case: `ans x` and type `ans y` again where x and y can be anything but empty for **non-MCQ cards** + + Expected: Cannot input an attempt to an already answered question. The error `Answer command is valid only when a + question is displayed` is shown in the status message. Status bar remains the same. + +=== Navigating to the next question in a test session + +. Go to the next question in a test session after answering the current question + +.. Prerequisites: Enter a test session by typing `test` when inside a folder. Display area enters fullscreen and +lowest scoring flashcard question and hints are displayed. Answer the current question by following +<> or reveal the answer so the current card has already been tested. +.. Test case: `next` where there are still cards left untested + + Expected: Next lowest scoring question and hints (if any) are displayed. Status bar remains the same to be still + in test session. +.. Test case: `next` where there are no more cards left to test + + Expected: Ends the current test session, so display area exits fullscreen and status bar changes to reflect tester + is back inside a folder. +.. Test case: `next` when prerequisite is not fulfilled where the current question is not yet answered or revealed + + Expected: The error `Next command is valid only when this question has been answered` is shown in the status message. Status bar remains the same. + + +//tag::reportmanualtest[] + +=== Viewing a report display + +. View report display + +.. Prerequisites: Enter a folder. The tester may want to ensure that they have at least two + test attempts on this folder, as a line graph requires at least two points to be drawn. (If there are fewer than two + attempts, it is not a bug if no line is drawn.) +.. Test case: `report` + + Expected: Enters report display, where the display area enters fullscreen. A graph is displayed showing maximum of + last ten test scores for this folder. A list, maximum of the three lowest scoring questions, is displayed. + `Report displayed` is shown on the status message and status bar shows that tester is in report display. +.. Test case: Other invalid commands such `test`, `add`, or a second `report` command. + + Expected: They should not be allowed. An error message will be displayed. +.. Test case: `end` + + Expected: Ends the current report session, where the display area exits fullscreen and be back inside the folder. `End + Report Session` is shown on the status message and status bar shows that tester is back inside the folder. + +// end::reportmanualtest[] + +//tag::sortmanualtest[] + +=== Sorting the cards + +. Sort + +.. Prerequisites: Enter a folder. The tester may want to ensure that they have at least two + cards in this folder, or else there is no way to tell if the sort happened. +.. Test case: `sort` + + Expected: Sorts cards in non-descending percentage score. If card A is above card B, card A will not have a higher + percentage score than card B. + `Sorted flashcards with lowest score first` is shown on the status message. + +// end::sortmanualtest[] + +//tag::foldermanualtesting[] +=== Folder operations + +. Adding a folder +.. Prerequisites: You must be at the home directory (status bar should read `In Home Directory`) and have only one folder called "Sample Folder". To get this configuration, you can delete all data files except for the jar file and relaunch the application. +.. Test case: `addfolder Folder 1` + + Expected: A folder by the name of "Folder 1" is added. Status message confirms that the folder added, and the new folder can be found in the list of folders. +.. Test case: `addfolder Sample Folder` + + Expected: No folder is added. Error details shown in the status message. +.. Other incorrect addfolder commands to try: `addfolder`, `addfolder Special/Chars`, `addfolder x` (where x is string longer than 50 characters) + + Expected: Similar to previous. -=== Saving data +. Deleting a folder +.. Prerequisites: You must be at the home directory (status bar should read `In Home Directory`) and have at least one folder. +.. Test case: `deletefolder 1` + + Expected: The folder at index 1 is deleted. Status message confirms that the folder is deleted, and the folder can no longer be found in the list of folders. +.. Test case: `deletefolder 0` + + Expected: No folder is deleted. Error details shown in the status message. +.. Other incorrect deletefolder commands to try: `deletefolder`, `deletefolder Sample Folder`, `deletefolder x` (where x is larger than the list length) + + Expected: Similar to previous. + +. Renaming a folder +.. Prerequisites: You must be at the home directory (status bar should read `In Home Directory`) and have at least one folder called "Sample Folder", and no folder called "New Name". +.. Test case: `editfolder x New Name` (where x is the index of a folder that is not Sample Folder) + + Expected: The folder at index x is renamed to "New Name". Status message confirms that renaming operation succeeded. +.. Test case: `editfolder x sample folder` (where x is the index of Sample Folder) + + Expected: The folder at index x is renamed to its original name (with new capitalisation). Status message confirms that renaming operation succeeded. +.. Test case: `editfolder x Sample Folder` (where x is not the index of Sample Folder) + + Expected: The folder at index x is not renamed. Error details shown in the status message. +.. Other incorrect editfolder commands to try: `editfolder`, `editfolder New Name 1`, `editfolder x New Name` (where x is larger than the list length) + + Expected: No folder is renamed. Error details shown in the status message. + +. Persistence of folder operations +.. Prerequisites: You must be at the home directory (status bar should read `In Home Directory`) and know where the data files are stored. By default, this will be at the `data/` directory at the path of the jar file. +.. Test case: `addfolder x` (where x is the name not used by any existing folder) + + Expected: A new json by the name of `x.json` appears in the data directory. +.. Test case: `deletefolder 1` + + Expected: The json with the same file name as the deleted folder is no longer present in the data directory. No other files in the directory are affected. +.. Test case: `editfolder 1 x` (where x is a name not used by any existing folder) + + Expected: The json with the original name of the edited folder is no longer present in the data directory, replaced with a json with the new folder name. No other files in the directory are affected. + + +=== Navigating in and out of folders + +. Entering a folder +.. Prerequisites: You must be at the home directory (status bar should read `In Home Directory`) and have at least one folder. +.. Test case: `cd 1` + + Expected: Folder 1 is entered. Status message confirms this and UI is updated. +.. Test case: `cd x` (where x is larger than the list length) + + Expected: No folder is entered. Error details shown in the status message. +.. Other incorrect cd commands to try: `cd`, `cd Sample Folder`, `cd ..` + + Expected: Similar to previous. + +. Exiting a folder +.. Prerequisites: You must be inside a folder (status bar should read `Inside Folder: x` where `x` is the name of the folder you are in). +.. Test case: `cd ..` + + Expected: The folder is exited and you return to the home directory. Status message confirms this and UI is updated. +.. Test case: `cd x` (where x is any combination of characters other than "..") + + Expected: The folder is not exited. Error details shown in the status message. +.. Other incorrect cd commands to try: `cd`, `cd Home` + + Expected: Similar to previous. -. Dealing with missing/corrupted data files -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ -_{ more test cases ... }_ +=== Saving data + +. Dealing with missing/corrupted data files +.. Prerequisites: You must know where the data files are stored. By default, this will be at the `data/` directory at the path of the jar file. +.. Test case: Delete the data directory and launch the app + + Expected: A sample folder is present when the app launches (although it is not committed to storage until a persistent change is made). +.. Test case: Insert a non-json file/corrupted json file in the data directory and launch the app + + Expected: The valid json files have folders with their corresponding names present. The non-json file/corrupted json file remains unaffected. + +//end::foldermanualtesting[] + +//tag::folderimportexporttesting[] +. Importing a folder +.. Prerequisites: + + You must be outside a folder (status bar should read `in home directory`. + + Ensure that you also have the file to import in the project root directory. + + file has to have a .csv extension format. + + The imported file has to comply with the csv file headers. + + Compulsory fields, like Question and Answer should not be left blank. + + folder should not already exist within the model. + +.. Test case: `import Blood.csv` + +Prerequisites: Ensure that Blood.csv exists within the project root directory. + +Expected: Command box outputs a message "Successfully imported: Blood.csv" and +a new folder with name Blood is added to the existing folders in the home directory, +with the corresponding flashcards added to the folder + +.. Other incorrect commands to try: `import`, +`import Blood.json` + +. Exporting a folder +.. Prerequisites: You must be outside a folder (status bar should read `in home directory`) + + flashcard application +.. Test case: `export 1` + +Prerequisites: You must have at least one folder existing in the application. + + Expected: The command box outputs a message "Successfully exported card folders" and project root directory should contain a `x.csv` file where `x` is the name of the first folder in the application. +.. Test case: `export 1 2 3` + +Prerequisites: You must have at least 3 card folders existing in the application. + + Expected: The same message output in the command box as the previous test case and the corresponding files `x1.csv`, `x2.csv` and `x3.csv` created in project root directory, where `x1`, `x2`, `x3` corresponds to the name of the first, second and third folder +.. Other incorrect commands to try: `export`, `export -1`, `export 12 13 14`, where `12` `13` and `14` are non existent card folder indexes. +//end:folderimportexporttesting[] diff --git a/docs/LearningOutcomes.adoc b/docs/LearningOutcomes.adoc index 2837c28b72c7..8004c019ddc1 100644 --- a/docs/LearningOutcomes.adoc +++ b/docs/LearningOutcomes.adoc @@ -164,7 +164,7 @@ class gives some examples of how to use _Equivalence Partitions_, _Boundary Valu Consider the link:{repoURL}/src/test/java/seedu/address/storage/StorageManagerTest.java[`StorageManagerTest.java`] class. -* Test methods `prefsReadSave()` and `addressBookReadSave()` are integration tests. Note how they simply test if The `StorageManager` class is correctly wired to its dependencies. +* Test methods `prefsReadSave()` and `cardFolderReadSave()` are integration tests. Note how they simply test if The `StorageManager` class is correctly wired to its dependencies. * Test method `handleAddressBookChangedEvent_exceptionThrown_eventRaised()` is a unit test because it uses _dependency injection_ to isolate the SUT `StorageManager#handleAddressBookChangedEvent(...)` from its dependencies. Compare the above with link:{repoURL}/src/test/java/seedu/address/logic/LogicManagerTest.java[`LogicManagerTest`]. Some of the tests in that class (e.g. `execute_*` methods) are neither integration nor unit tests. They are _integration + unit_ tests because they not only check if the LogicManager is correctly wired to its dependencies, but also checks the working of its dependencies. For example, the following two lines test the `LogicManager` but also the `Parser`. @@ -228,7 +228,7 @@ When the value represented by the `ObservableValue` changes, it will notify all * *MVC Pattern* : ** The 'View' part of the application is mostly in the `.fxml` files in the `src/main/resources/view` folder. ** `Model` component contains the 'Model'. However, note that it is possible to view the `Logic` as the model because it hides the `Model` behind it and the view has to go through the `Logic` to access the `Model`. -** Sub classes of link:{repoURL}/src/main/java/seedu/address/ui/UiPart.java[`UiPart`] (e.g. `PersonListPanel` ) act as 'Controllers', each controlling some part of the UI and communicating with the 'Model' (via the `Logic` component which sits between the 'Controller' and the 'Model'). +** Sub classes of link:{repoURL}/src/main/java/seedu/address/ui/UiPart.java[`UiPart`] (e.g. `CardListPanel` ) act as 'Controllers', each controlling some part of the UI and communicating with the 'Model' (via the `Logic` component which sits between the 'Controller' and the 'Model'). * *Abstraction Occurrence Pattern* : Not currently used in the app. *Resources* diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc old mode 100644 new mode 100755 index 7e0070e12f49..588b0c69969e --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,8 +1,9 @@ -= AddressBook Level 4 - User Guide += Know-It-All - User Guide :site-section: UserGuide :toc: :toc-title: :toc-placement: preamble +:toclevels: 4 :sectnums: :imagesDir: images :stylesDir: stylesheets @@ -12,33 +13,81 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4 +:repoURL: https://github.com/cs2103-ay1819s2-w10-4/main -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `CS2103-AY2018/19s2-W10-4 Team` Since: `Mar 2019` Licence: `MIT` +// tag::introabout[] == Introduction -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +*Know-It-All* is a flashcard application that helps users store and organise their learning material. With an easy to use interface and convenient content sharing functionality, *Know-It-All* is designed to help students perform rote learning more efficiently. From cramming in between lessons to focused study, the interactive test session boosts the effectiveness of repetition and recall for memorisation. *Know-It-All* targets medicine students as their studies involve a considerable amount of memory work, and deals with content that is suitable for the bite-sized flashcard format. + +image::Ui.png[width="790"] + +== How To Use This Guide + +Welcome to the *Know-It-All* User Guide! This document will equip you with what you need in order to use `v1.4` of the application. While some familiarity with command line programs will come in handy, simply adhere to the command formats specified in this guide closely and the rest will be a breeze. + +// end::introabout[] + +Look out for the following icons and formatting used! + +[NOTE] +Important information that should be noted. + +[TIP] +Tips that can help if you get stuck. + +`test`: Command to be executed or less commonly, a component, class or object in the architecture of the application. + +==== +Useful information for a deeper understanding of the command. +==== + +Without further ado, let's head over to <> to get started! == Quick Start -. Ensure you have Java version `9` or later installed in your Computer. -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. +==== Launching the application +To get *Know-It-All* up and running: + +. Ensure that you have Java version `9` or later installed in your Computer. +. Download the latest `knowitall.jar` https://github.com/cs2103-ay1819s2-w10-4/main/releases[here]. +. Copy the file to the folder you want to use as the home folder for *Know-It-All*. . Double-click the file to start the app. The GUI should appear in a few seconds. + +// tag::quickstart[] +==== Using the application +In this section, we'll walk you through the primary user interface of the application and how to create your first <>. + +[TIP] +To use a command, 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. + + +. When booting up the app for the first time, you should see the <> much like the screengrab below. The first and only <> present is a sample folder. + -image::Ui.png[width="790"] +image::AnnotatedStartupUI.png[width="500"] + +. `cd 1` : enters the 1st flashcard folder. You will see a change in the user interface as you enter the folder. + -. Type the command in the command box and press kbd:[Enter] to execute it. + -e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. -. Some example commands you can try: +image::AnnotatedEnterFolderUI.png[width="500"] -* *`list`* : lists all contacts -* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the Address Book. -* **`delete`**`3` : deletes the 3rd contact shown in the current list -* *`exit`* : exits the app +. `add q/How many chambers are there in a heart? a/Four` : adds a new flashcard to the current folder. ++ +image::AnnotatedAddCardUI.png[width="500"] -. Refer to <> for details of each command. +. *`select 4`* : selects the 4th flashcard in the current folder, which is also your newly added card. ++ +image::AnnotatedSelectCardUI.png[width="500"] + +. *`exit`* : exits the app. The app window will close. + +This is the end of the Quick Start tutorial. Please refer to <> for details of each command, and feel free to reach out to us if you run into any issues! +// end::quickstart[] + +[NOTE] +Your data is saved in the `data/` folder by default. Like any other software application, you are not advised to +modify any of the system created files located within the folder. If you do, *Know-It-All* may not perform right! [[Features]] == Features @@ -46,135 +95,275 @@ e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. ==== *Command Format* -* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. -* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. -* Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. -* Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +* Commands are written in monospaced font, e.g. `sort` +* Words in `UPPER_CASE` are the parameters to be supplied by you e.g. in `addfolder FOLDER_NAME`, `FOLDER_NAME` is a +parameter which can be something like `Human Anatomy`. +* Items in square brackets are optional e.g `HINTS` in `add q/QUESTION a/ANSWER [h/HINTS]`. +* Items with `…​` after them can be used multiple times including zero times e.g. in `add q/QUESTION a/ANSWER [i/INCORRECT_OPTION]...`, you can include zero or more `i/INCORRECT_OPTIONs`. +* Parameters can be in any order e.g. if the command specifies `q/QUESTION a/ANSWER`, `a/ANSWER q/QUESTION` is also acceptable. ==== -=== Viewing help : `help` +[NOTE] +This application only supports Unicode characters compatible with XML. In general, most characters visible on your keyboard are supported! +Please refer to https://www.w3.org/TR/unicode-xml/#Suitable[here for more details on incompatible characters]. -Format: `help` +//tag::folderoperations[] +=== Folder Operations +Commands listed in this section are folder-level operations. This includes the operations such as creating and deleting of folders, and excludes commands that affect the contents of individual folders (e.g. adding a card). + +[NOTE] +The commands in this section, unless otherwise stated, can only be executed when you are at the home directory, outside of any folder. The commands are also not valid inside a test or report session. You can easily verify that you are at the home directory with the status bar at the bottom, which should display: + + + +image:StatusBarInHomeDirectory.png[width ="150"] -=== Adding a person: `add` +==== Enter folder : `cd` -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +Enters the folder specified by <>. Panel on the left will display the list of cards in that folder. -[TIP] -A person can have any number of tags (including 0) +Format: `cd FOLDER_INDEX` Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +* `cd 2` + +Enters the second folder in the folder list on the home directory. -=== Listing all persons : `list` +==== Exit folder : `cd ..` -Shows a list of all persons in the address book. + -Format: `list` +Return to the root directory (exit the current folder). A list of folders will be displayed + +Format: `cd ..` + +==== +* This command, unlike the rest of the commands in this section, can only be executed when inside a folder. +==== + +Examples: + +* `cd 2` + +`cd ..` + +The first command enters the second folder in the folder list on the home directory. The second command then returns you back to the home directory by exiting the folder. + +//tag::addfolder[] +[[addfolder]] +==== Create new folder : `addfolder` + +Creates a new flashcard folder with the specified name. + +Format: `addfolder FOLDER_NAME` + +==== +* The newly created folder will not contain any cards. +* Folder names must be unique, between 1 and 50 characters, and only contain letters, numbers and whitespace. Folder names with the same characters but different capitalisation are non-unique. Attempting to add a folder with any of the above rules violated will result in an error. +* Each folder and its cards are stored independently in the directory specified in `preferences.json`. By default, this is the `data/` directory. +==== + +Examples: + +* `addfolder Nervous System` + +Creates a folder with the name "Nervous System". The UI should appear like the following after the command is executed. + +image:AnnotatedAddFolderUI.png[width=500] + +You can then enter the folder with the `cd` command and begin adding cards. +//end::addfolder[] + +==== Remove folder : `deletefolder` + +Removes the flashcard folder specified by index. + +Format: `deletefolder FOLDER_INDEX` + +==== +* When a folder is deleted, all its cards are removed as well. +==== + +Examples: + +* `deletefolder 2` + +Deletes the second folder in the folder list, along with its cards, on the home directory. + +//tag::editfolder[] +==== Rename folder : `editfolder` -=== Editing a person : `edit` +Renames the flashcard folder specified by index. -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +Format: `editfolder FOLDER_INDEX NEW_FOLDER_NAME` -**** -* 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, ... +==== +* The new name of the folder cannot be the same as an existing folder, and must adhere to the rules specified in <>. +* You are allowed to rename an existing folder to a different capitalisation of its own name. +==== + +Examples: + +* `editfolder 2 Circulatory System` + +Renames the second folder in the folder list to "Circulatory System". +//end::editfolder[] + +**Merge folders feature** `Coming in v2.0` + +This feature will enable users to join multiple folders together, reducing the number of folders and grouping two topics. + +Format: `merge FOLDER_INDEX_1 FOLDER_INDEX_1 NEW_FOLDER_NAME` + +//end::folderoperations[] + +=== Card Operations +Commands listed in this section affect the flashcards within a single folder. + +[NOTE] +The commands in this section can only be executed when you are within a folder. The commands are also not valid inside a test or report session. You can easily verify you are inside a folder with the status bar at the bottom, which should display: + + + +image:StatusBarInFolder.png[width ="200"] + +// tag::add[] +==== Adding a flashcard : `add` + +Adds a flashcard to the current folder. *Know-It-All* supports 2 types of flashcards: **Single answer cards** and **MCQ** cards. + +Format **(Single answer)**: `add q/QUESTION a/ANSWER [h/HINT]` + +Results in the following card: + +image:singleAnswerCard.PNG[width="250"] + + +Format **(MCQ)**: `add q/QUESTION a/ANSWER [i/INCORRECT_OPTION]... [h/HINT]` + +Results in the following card: + +image:mcqCard.PNG[width="250"] + +==== +* The question, answer, incorrect option, and hint fields can take any character, but cannot be blank. +* Each question, answer, incorrect option and hint must be 256 characters or less (including spaces). +* If the card to be added has the same question and answer as an existing card, the card to be added will be considered a duplicate card, and the add attempt will be invalid. +* A card can have at most 1 hint (including 0). +* A card can have at most 3 incorrect options to denote an MCQ card. +* A card with 0 incorrect options will automatically be denoted as a Single answer card. +* If the content of a card exceeds the length of the card, you can scroll/click and drag to see the rest of the content. +==== + +Examples: + +* `add q/Hello? a/World` +* `add q/The cat ___ on the mat a/sat h/poetry` +* `add q/What is the powerhouse of the cell? a/mitochondria i/cell wall i/nucleus h/biology` + +**Adding Fill-In-The-Blanks style card** `Coming in v2.0` + +Allows you to add a card with blanks for multiple answers to be given during a test session. Questions for such cards would include underscores "_", each signifying a blank to be filled with an answer. + +Format: ``add q/QUESTION_WITH_BLANKS a/ANSWER_1/ANSWER_2/ANSWER_3...` + +Example: + + +* `add q/The quick brown _ jumps over the lazy _. a/fox/dog` + +**Adding images to cards** `Coming in v2.0` + +Allows you to add images to cards to supplement the text content of the card. + +Format: `add q/QUESTION a/ANSWER [img/IMAGE_FILE_PATH]...` + +Example: + + +* `add q/Hello? a/World img/diagram.jpg` +// end::add[] + +// tag::edit[] +==== Editing a flashcard : `edit` + +Edits the flashcard specified by the index in the current folder. + +Format: `edit INDEX [q/QUESTION] [a/ANSWER] [h/HINT]` + +==== +* Edits the card at the specified `INDEX`. The index refers to the index number shown in the displayed card 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. -**** +* **(MCQ cards)** When editing incorrect options, the existing incorrect options of the card will be removed i.e adding of options is not cumulative. +* You can remove the card's hint by typing `h/` without specifying any hint after it. +* You can remove the card's incorrect options by typing `i/` without specifying any incorrect option after it. +==== 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. +* `edit 1 a/Skin h/` + +Edits the answer of the 1st card to be 'Skin' and removes the hint associated, if any. +* `edit 2 h/history q/Who discovered Penicillin? a/Alexander Fleming` + +Edits the hint, question and answer of the 2nd card respectively. +* `edit 3 h/cells h/biology h/organs` + +Replaces the hint of the current card with "organs" only. +// end::edit[] -=== Locating persons by name: `find` +==== Selecting a flashcard : `select` -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +Displays flashcard details (question, answer, hint, <>) on the right panel on selection by index. -**** -* 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` -**** +Format: `select INDEX` Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `list` + +`select 2` + +Selects the 2nd card in the current folder -=== Deleting a person : `delete` +==== Deleting a flashcard : `delete` + +Deletes the flashcard identified by index from the current folder. -Deletes the specified person from the address book. + Format: `delete INDEX` -**** -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. +==== +* Deletes the card at the specified `INDEX`. +* The index refers to the index number shown in the displayed card list. * The index *must be a positive integer* 1, 2, 3, ... -**** - -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. +Deletes the 2nd card in the address book. -=== Selecting a person : `select` +//tag::sortcommand[] +==== Sort flashcards by score within a folder : `sort` -Selects the person identified by the index number used in the displayed person list. + -Format: `select INDEX` +Displays all flashcards sorted such that the lowest <> are at the top temporarily. -**** -* Selects the person and loads the Google search page the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index *must be a positive integer* `1, 2, 3, ...` -**** +Format: `sort` +//end::sortcommand[] -Examples: +==== Search by keywords : `search` -* `list` + -`select 2` + -Selects the 2nd person in the address book. -* `find Betsy` + -`select 1` + -Selects the 1st person in the results of the `find` command. +Within a folder, searches for flashcards inside the current folder using keywords in flashcard questions. -=== Listing entered commands : `history` +Format: `search KEYWORDS [MORE_KEYWORDS]` -Lists all the commands that you have entered in reverse chronological order. + -Format: `history` +==== List flashcards : `list` + +Display a list of the flashcards in the current folder, where only questions can be seen, answers are hidden. + +Format: `list` -[NOTE] ==== -Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and next input respectively in the command box. +* This command is implicitly invoked upon entering a folder, and can be used to reset the view after search or sort. ==== // tag::undoredo[] -=== Undoing previous command : `undo` +==== Undoing previous command : `undo` + +Restores the cards in a particular card folder to the state before the previous _undoable_ command was executed. -Restores the address book to the state before the previous _undoable_ command was executed. + Format: `undo` +==== +* This command is performed with respect to the present folder you are in. For example, if you perform an `add` operation in folder A and enter folder B, invoking the `undo` command will undo the previous _undoable_ command performed in folder B and not the aforementioned `add` operation. +==== + [NOTE] ==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). +Undoable commands: commands that modify a card folder's content (`add`, `delete` and `edit`). ==== +[NOTE] +At the end of a successful test session, scores are final and you will not be able to perform an `undo` to +restore the previous states before the test session. + Examples: * `delete 1` + @@ -191,11 +380,16 @@ The `undo` command fails as there are no undoable commands executed previously. `undo` (reverses the `clear` command) + `undo` (reverses the `delete 1` command) + -=== Redoing the previously undone command : `redo` +==== Redoing the previously undone command : `redo` + +Reverses the most recent `undo` command performed in a folder. -Reverses the most recent `undo` command. + Format: `redo` +==== +* As with the `undo` command, this command is performed with respect to the present folder you are in. For example, if you perform an `undo` operation in folder A and enter folder B, invoking the `redo` command will redo the previous `undo` command performed in folder B and not the one in folder A. +==== + Examples: * `delete 1` + @@ -214,47 +408,331 @@ The `redo` command fails as there are no `undo` commands executed previously. `redo` (reapplies the `clear` command) + // end::undoredo[] -=== Clearing all entries : `clear` -Clears all entries from the address book. + -Format: `clear` +//tag::advancedoperations[] -=== Exiting the program : `exit` +==== Export flashcards : `export` +Exporting flashcards is a great way to start sharing your flashcards with others. -Exits the program. + -Format: `exit` +The export command creates a csv file containing the flashcards from the specified folder in your project root directory. + + +Format: `export FOLDER_INDEX FILENAME [MORE_INDEXES]` + +[NOTE] +The export command creates the new csv file in your project root directory. +i.e The directory where your .jar file is located. + +The import command imports csv files located in the same directory as well. -=== Saving the data +image::project_root_dir.png[width ="800"] -Address book data are saved in the hard disk automatically after any command that changes the data. + -There is no need to save manually. +[NOTE] +The current version does not support the importing and exporting of files outside of this directory + +==== +* You should key in indices corresponding to the folder index +* Negative numbers are not allowed +==== -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +Examples: -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +* `export 1 2 3` + +exports the first, second and third cardfolder in your home directory. +Suppose that the first, second and third cardfolder corresponds to the card folder names : + +Blood + +Circulatory System + +Cardiovascular + +Then the following files Blood.csv, Circulatory System.csv and Cardiovascular.csv +will be created in the project root directory. -== FAQ +==== Import flashcards : `import` +Besides being able to import flashcards exported by others, the import command provies a faster way of +creating multiple flashcards. -*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. +You type your flashcards out on excel and later save it in your project root directory, allowing you +to import it over to your application. -== Command Summary -* *Add* `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + -e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -* *Clear* : `clear` -* *Delete* : `delete INDEX` + -e.g. `delete 3` -* *Edit* : `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + -e.g. `edit 2 n/James Lee e/jameslee@example.com` -* *Find* : `find KEYWORD [MORE_KEYWORDS]` + -e.g. `find James Jake` -* *List* : `list` -* *Help* : `help` -* *Select* : `select INDEX` + -e.g.`select 2` -* *History* : `history` -* *Undo* : `undo` -* *Redo* : `redo` +NOTE: The csv file imported should follow the format described below + +==== +* The first row of the csv file should have the following headers: ++ +Question, Answer, Hints, Option, Option, Option. +* Question and Answer are mandatory fields, and should not be left blank. +* Hints can take 0 or 1 values only. +* For MCQ cards, The csv file accepts up to 3 incorrect options per flashcard. +==== + +.Sample format for csv file +image::Blood.png[width = "800"] + +Format: `import FILENAME` + +NOTE: unlike the export command the importing of multiple csv files +is not supported in *Know-It-All*. + +Filename imported is case insensitive. + +Examples: + +* `import Blood.csv` + +Imports blood csv file into *Know-It-All*. A new Blood cardfolder should be present in the home +directory after execution of this command. +// end::advancedoperations[] + + +//tag::testoperations[] +=== Test Operations +After memorising the content of the flashcards, it is helpful to test how much information have been internalised and retained in a timed setting. The following commands show just how this can be done with the <> functionality of *Know-It-All*. + +[NOTE] +At the end of a successful test session, scores are final and you will not be able to perform an `undo` to restore the +previous states before the test session. + +==== Test flashcards in a folder : `test` + +You will enter a test session, where the display area enters a fullscreen and you will be presented flashcard +questions and hints (if any) one by one. You should see a screen like the figure below. + +.A successful test command will display a test session page +image::startTestSessionPage.png[width="500"] + +{empty} + +Format: `test` + +[NOTE] +This command is only considered valid when inside the folder to be tested and is not already inside a test session. + +[NOTE] +This command is invalid if the current folder is empty as there will be no flashcards to test. + +==== +* Hints will be presented along with the questions. +* When presented with a question in a test session, you can either input an attempt or enter the command to reveal the +answer. +* For **MCQ cards**, the ordering of options will be randomized each time the flashcard is tested. +* Internally, flashcards in a folder are queued to be displayed one by one in the order of lowest existing score to highest existing score. +* The next flashcard will only be presented when the next command is carried out. +==== + + +Examples: + +* `test` + +**Hint toggle on / off feature** `Coming in v2.0` + +If you are familiar with the content and feeling confident, +simply toggle off hints during the test session. You can do it by specifying ‘-nohint’ at the end of the +test command. Hint will not be displayed along with the question when the card is presented. + +Format: `test [-nohint]` + +**Timer feature** `Coming in v2.0` + +If you are preparing for an exam that will require you to recall information quickly within the limited time given, +this timer feature is just right for you! You will be given only 20 seconds to answer each question. If the 20 seconds is up before the question is answered, this attempt will be marked as wrong. + +Format: `test [-timer]` + +==== Keying in answer to a flashcard: `ans` + +To reinforce learning and provide a more engaging experience with *Know-It-All*, you can input an answer for the +currently displayed flashcard question. *Know-It-All* compares your attempt with the correct answer for that flashcard and +tells you if you are right or wrong. + +If the answer has been submitted successfully and it is *correct*, you will see the following page. + +.Correct Answer page +image::CorrectAnswerPage.png[width="500"] + +{empty} + + +If the answer has been submitted successfully and it is *wrong*, you will see the following page. + +.Wrong Answer page +image::WrongAnswerPage.png[width="500"] + +{empty} + +Format: `ans ANSWER` + +[NOTE] +This command is only considered valid if a card question is currently being +displayed in an active test session. + +==== +* Answer matching is case insensitive. +* Answering a flashcard will increase the total number of attempts. If your answer is correct, this action will also +increase the number of correct attempts. +* To answer **MCQ cards**, enter the number of the option that you think is correct, rather than the option itself. + +E.g. `ans 1` rather than `ans myanswer` +==== + +Examples: + +* `ans Mitochondrion` + +in response to the card question: What is the powerhouse of the cell? + +* `ans 2` + +in response to the card displayed below, choosing option 2 will give the right answer as the correct answer is +'Pigs'. + +.Answering an MCQ card +image::AnsweringMcqCard.png[width="500"] +//end::testoperations[] +==== Reveal answer to a flashcard : `reveal` +If you have no clue what the answer is, this command immediately reveals the correct answer, as seen in the figure +below. You will not need attempt any answer before being presented the correct answer. + +.Revealed Answer page +image::RevealAnswerPage.png[width="500"] + +{empty} + +Format: `reveal` + +[NOTE] +This command is only considered valid if a card question is currently being displayed in an active test session. + +==== +* This is equivalent to a wrong answer attempted, so there is no addition to the correct attempts of this card. +==== + +==== Go to next flashcard : `next` + +You will be presented with the next lowest scoring flashcard in this current test session. Upon a successful next +command, you should see a similar page below. + +.Next card question displayed upon a successful next command +image::NextCommandPage.png[width="500"] + +{empty} + +Format: `next` + +[NOTE] +This command is only considered valid if a card question and answer is currently being displayed (has already done answering the question or revealed the answer) in an active test session. In other words, a flashcard cannot be skipped. + +==== +* If all cards have already been tested, a next command will be equivalent to an end command, ending the current test + session. +* There is no backtracking in the current session so there is no `prev` command. +==== + +[[EndCommand]] +==== End the current test session : `end` + +Quits the current test session and you will be back inside the card folder. You should see a page like below. + +.A successful end command brings you back to inside the folder +image::EndCommandPage.png[width="500"] + +{empty} + +Format: `end` + +[NOTE] +For the final <> to be recorded, you must have attempted at least 1/4 of cards in the + card folder. If there are less than 4 cards, any number of cards attempted will be recorded. + +//tag::reportoperations[] +=== Report Operations +After testing, you can track how you scored against previous attempts using our report feature. Because test +sessions are run for cards in a folder, the <> are tracked per folder. + +==== Display a test score report : `report` + +Displays a full-screen <> report for the current folder. +The report comprises a graph showing a maximum of the last 10 <>, +the latest score change, and a maximum of 3 lowest individual scoring cards and their individual <>. +An example is shown below: + +.Report display +image::ReportDisplay.png[width="790"] + +[TIP] +The report display is currently best viewed with the window in full screen. Otherwise, you may need to use the horizontal and vertical +scroll bars to view the graph and questions. Hang tight, a display that changes in size is coming in v2.0! + +**Displays response time for each card** `Coming in v2.0` + +With the timer feature coming in v2.0, we are also able to track the time taken to provide the correct response to the + question. This provides yet another metric, in addition to correctness, to judge your understanding of the topic. + + +Format: `report` + +[NOTE] +This command is only valid inside a folder. + +==== +* There must be at least two test attempts for the line graph to be drawn. +==== +==== End the current report session : `end` + +Quits the current report session. + +Format: `end` + +[NOTE] +This command is only valid inside a report display. + +//end::reportoperations[] + + +=== Global operations +These commands are valid from anywhere in the application. + +==== Viewing help : `help` + +Opens the User Guide in a new window. + +Format: `help` + +==== Exiting the program : `exit` + +Exits the program. + +Format: `exit` + +== Glossary + +* [[flashcard]] **Flashcard/Card**: An object containing a single question and answer, and optionally hints. +* [[folder]] **Folder**: A collections of flashcards. There are no sub-folders. +* [[testsession]] **Test Session**: A session where all flashcards in a folder are queued to have their +questions displayed. You are required to key in an answer for each question. +* [[cardscore]] **Card Score**: The number of correct answers divided by the number of attempts for a single card. When you are tested on a card, +this number is automatically calculated and recorded. +* [[testscore]] **Test Score**: The number of cards correctly answered over number of cards attempted during a test session. This number is automatically recorded after each test session. +* [[homedirectory]] **Home Directory**: The home page where all the folders are listed. From here, users can enter folders to view cards. +* [[index]] **Index**: The unique number associated with an item in a list. The first item in a list has an index of 1. + +//tag::cmdsummary[] +== Command Summary +[width="100%",cols="20%,<30%",options="header",] +|======================================================================= +|Command | Summary +|`add q/QUESTION a/ANSWER [h/HINT] [i/INCORRECT_OPTION]...` | Adds a flashcard to the current folder. +|`edit INDEX [q/QUESTION] [a/ANSWER] [h/HINT] [i/INCORRECT_OPTION]...` | Edits the flashcard specified by the index in the current folder. +|`select INDEX` | Displays flashcard details (question, answer, hint, card score) on the right panel on selection by index. +|`delete INDEX` | Deletes the flashcard identified by index from the current folder. +|`sort` | Displays all flashcards sorted such that the lowest scoring cards are at the top temporarily. +|`search KEYWORDS [MORE_KEYWORDS]` | Searches for flashcards inside the current folder using keywords in flashcard questions. +|`list` | Display a list of the flashcards in the current folder +|`report` | Display a test score report for the current folder +|`undo` | Undoes the previous undoable command. +|`redo` | Redoes the last `undo`. +|`cd ..` | Return to the root directory (exit the current folder). A list of folders will be displayed. +|`cd FOLDER_INDEX`|Enters the folder specified by index. Panel on the left will display the list of cards in that folder. +|`addfolder FOLDER_NAME` | Creates a new flashcard folder with the specified name. +|`deletefolder FOLDER_INDEX` | Removes the flashcard folder specified by index. +|`editfolder FOLDER_INDEX NEW_FOLDER_NAME`| Renames the flashcard folder specified by index to the new name specified. +|`test` | This command begins a test session, where the display area enters a fullscreen. +|`ans ANSWER` | Enter answer for a flashcard. +|`reveal` | Immediately reveals the correct answer. +|`next` | Presents the next lowest score flashcard in this current test session. +|`end` | Quits the current test session or report display. +|`search KEYWORDS [MORE_KEYWORDS]` | Searches for flashcards inside the current folder using keywords in flashcard questions. +|`import FILENAME` | Imports a file with the specified name. Filename must include .csv extension +|`export FOLDER_INDEX [MORE_INDEXES]` | Creates a csv file containing the flashcards from the specified folder, which can later be imported. +|`help` | Opens the User Guide in a new window. +|`exit` | Exits the application. +|======================================================================= +//end::cmdsummary[] diff --git a/docs/UsingCheckstyle.adoc b/docs/UsingCheckstyle.adoc index 5e2f02bd279c..52a64107ca60 100644 --- a/docs/UsingCheckstyle.adoc +++ b/docs/UsingCheckstyle.adoc @@ -20,7 +20,7 @@ Restart the IDE to complete the installation. + 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. cardfolder . Select `Use a local Checkstyle file` . Use the checkstyle configuration file found at `config/checkstyle/checkstyle.xml` . Click `Next` > `Finish` @@ -28,7 +28,7 @@ image::checkstyle-idea-scan-scope.png[width="500"] + image::checkstyle-idea-configuration.png[width="700"] . Click `OK` - +b == Troubleshooting Checkstyle-IDEA **Problem: When importing `checkstyle.xml`, Checkstyle-IDEA plugin complains that `The Checkstyle rules file could not be parsed. ... The file has been blacklisted for 60s.`** diff --git a/docs/UsingGradle.adoc b/docs/UsingGradle.adoc index d1be2f3b7c3a..7f9b3bb3988f 100644 --- a/docs/UsingGradle.adoc +++ b/docs/UsingGradle.adoc @@ -83,9 +83,9 @@ The set of code style rules implemented can be found in `config/checkstyle/check * **`allTests`** + Runs all tests. * **`guiTests`** + -Runs all tests in the `seedu.address.ui` and `systemtests` package +Runs all tests in the `seedu.knowitall.ui` and `systemtests` package * **`nonGuiTests`** + -Runs all non-GUI tests in the `seedu.address` +Runs all non-GUI tests in the `seedu.knowitall` package * **`headless`** + Sets the test mode as _headless_. The mode is effective for that Gradle run only so it should be combined with other test tasks. diff --git a/docs/diagrams/CardOperationsSequenceDiagrams.pptx b/docs/diagrams/CardOperationsSequenceDiagrams.pptx new file mode 100644 index 000000000000..b0ef6bab5a77 Binary files /dev/null and b/docs/diagrams/CardOperationsSequenceDiagrams.pptx differ diff --git a/docs/diagrams/FolderOperationsSequenceDiagrams.pptx b/docs/diagrams/FolderOperationsSequenceDiagrams.pptx new file mode 100644 index 000000000000..a048d2d47cb0 Binary files /dev/null and b/docs/diagrams/FolderOperationsSequenceDiagrams.pptx differ diff --git a/docs/diagrams/HighLevelSequenceDiagrams.pptx b/docs/diagrams/HighLevelSequenceDiagrams.pptx index 38332090a79a..e83c5f0c34e1 100644 Binary files a/docs/diagrams/HighLevelSequenceDiagrams.pptx and b/docs/diagrams/HighLevelSequenceDiagrams.pptx differ diff --git a/docs/diagrams/LogicComponentClassDiagram.pptx b/docs/diagrams/LogicComponentClassDiagram.pptx index 6fcc1136a5bb..b9f81b0171e3 100644 Binary files a/docs/diagrams/LogicComponentClassDiagram.pptx and b/docs/diagrams/LogicComponentClassDiagram.pptx differ diff --git a/docs/diagrams/ModelAndStorageEnhancementsDiagrams.pptx b/docs/diagrams/ModelAndStorageEnhancementsDiagrams.pptx new file mode 100644 index 000000000000..2b7c5206899e Binary files /dev/null and b/docs/diagrams/ModelAndStorageEnhancementsDiagrams.pptx differ diff --git a/docs/diagrams/ModelComponentClassDiagram.pptx b/docs/diagrams/ModelComponentClassDiagram.pptx index dc0e4ac5ea66..73b656f1b2c1 100644 Binary files a/docs/diagrams/ModelComponentClassDiagram.pptx and b/docs/diagrams/ModelComponentClassDiagram.pptx differ diff --git a/docs/diagrams/NextCommandSequenceDiagram.pptx b/docs/diagrams/NextCommandSequenceDiagram.pptx new file mode 100644 index 000000000000..fc6ea5716a6a Binary files /dev/null and b/docs/diagrams/NextCommandSequenceDiagram.pptx differ diff --git a/docs/diagrams/ReportCommandSequenceDiagram.pptx b/docs/diagrams/ReportCommandSequenceDiagram.pptx new file mode 100755 index 000000000000..953abd622da9 Binary files /dev/null and b/docs/diagrams/ReportCommandSequenceDiagram.pptx differ diff --git a/docs/diagrams/ReportDisplaySequenceDiagram.pptx b/docs/diagrams/ReportDisplaySequenceDiagram.pptx new file mode 100644 index 000000000000..9d6b2005cb9d Binary files /dev/null and b/docs/diagrams/ReportDisplaySequenceDiagram.pptx differ diff --git a/docs/diagrams/StorageComponentClassDiagram.pptx b/docs/diagrams/StorageComponentClassDiagram.pptx index 4afecd63e210..0f0d84075435 100644 Binary files a/docs/diagrams/StorageComponentClassDiagram.pptx and b/docs/diagrams/StorageComponentClassDiagram.pptx differ diff --git a/docs/diagrams/Test session images for User guide.pptx b/docs/diagrams/Test session images for User guide.pptx new file mode 100755 index 000000000000..808bf0e52afb Binary files /dev/null and b/docs/diagrams/Test session images for User guide.pptx differ diff --git a/docs/diagrams/TestSessionSequenceDiagram.pptx b/docs/diagrams/TestSessionSequenceDiagram.pptx new file mode 100755 index 000000000000..f0ab3303c924 Binary files /dev/null and b/docs/diagrams/TestSessionSequenceDiagram.pptx differ diff --git a/docs/diagrams/UiComponentClassDiagram.pptx b/docs/diagrams/UiComponentClassDiagram.pptx old mode 100644 new mode 100755 index ab325e889f70..d8197fd74650 Binary files a/docs/diagrams/UiComponentClassDiagram.pptx and b/docs/diagrams/UiComponentClassDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoSequenceDiagram.pptx b/docs/diagrams/UndoRedoSequenceDiagram.pptx index 5ccc1042caac..291e8890bad4 100644 Binary files a/docs/diagrams/UndoRedoSequenceDiagram.pptx and b/docs/diagrams/UndoRedoSequenceDiagram.pptx differ diff --git a/docs/images/AddCommandSequenceDiagram.png b/docs/images/AddCommandSequenceDiagram.png new file mode 100644 index 000000000000..f4d0ace179c7 Binary files /dev/null and b/docs/images/AddCommandSequenceDiagram.png differ diff --git a/docs/images/AddFolderSequenceDiagram.png b/docs/images/AddFolderSequenceDiagram.png new file mode 100644 index 000000000000..2dae12ea21eb Binary files /dev/null and b/docs/images/AddFolderSequenceDiagram.png differ diff --git a/docs/images/AnnotatedAddCardUI.png b/docs/images/AnnotatedAddCardUI.png new file mode 100644 index 000000000000..6bedcf00b255 Binary files /dev/null and b/docs/images/AnnotatedAddCardUI.png differ diff --git a/docs/images/AnnotatedAddFolderUI.png b/docs/images/AnnotatedAddFolderUI.png new file mode 100644 index 000000000000..6911c270aa66 Binary files /dev/null and b/docs/images/AnnotatedAddFolderUI.png differ diff --git a/docs/images/AnnotatedEnterFolderUI.png b/docs/images/AnnotatedEnterFolderUI.png new file mode 100644 index 000000000000..3a1bc14176cb Binary files /dev/null and b/docs/images/AnnotatedEnterFolderUI.png differ diff --git a/docs/images/AnnotatedSelectCardUI.png b/docs/images/AnnotatedSelectCardUI.png new file mode 100644 index 000000000000..df9a76b57672 Binary files /dev/null and b/docs/images/AnnotatedSelectCardUI.png differ diff --git a/docs/images/AnnotatedStartupUI.png b/docs/images/AnnotatedStartupUI.png new file mode 100644 index 000000000000..74006e362c34 Binary files /dev/null and b/docs/images/AnnotatedStartupUI.png differ diff --git a/docs/images/AnswerCommandSequenceDiagram.png b/docs/images/AnswerCommandSequenceDiagram.png new file mode 100755 index 000000000000..4b64b5841128 Binary files /dev/null and b/docs/images/AnswerCommandSequenceDiagram.png differ diff --git a/docs/images/AnsweringMcqCard.png b/docs/images/AnsweringMcqCard.png new file mode 100644 index 000000000000..1e8fb18e3a5c Binary files /dev/null and b/docs/images/AnsweringMcqCard.png differ diff --git a/docs/images/Architecture.png b/docs/images/Architecture.png index d9215a274f93..984482cd335a 100644 Binary files a/docs/images/Architecture.png and b/docs/images/Architecture.png differ diff --git a/docs/images/Blood.png b/docs/images/Blood.png new file mode 100644 index 000000000000..65c264d97855 Binary files /dev/null and b/docs/images/Blood.png differ diff --git a/docs/images/CardImplementationAddExample.png b/docs/images/CardImplementationAddExample.png new file mode 100644 index 000000000000..daa8a85005ee Binary files /dev/null and b/docs/images/CardImplementationAddExample.png differ diff --git a/docs/images/ChangeCommandSequenceDiagram.png b/docs/images/ChangeCommandSequenceDiagram.png new file mode 100644 index 000000000000..a33d649782e7 Binary files /dev/null and b/docs/images/ChangeCommandSequenceDiagram.png differ diff --git a/docs/images/CorrectAnswerPage.png b/docs/images/CorrectAnswerPage.png new file mode 100755 index 000000000000..c39dbe449be9 Binary files /dev/null and b/docs/images/CorrectAnswerPage.png differ diff --git a/docs/images/DeleteCardSdForLogic.png b/docs/images/DeleteCardSdForLogic.png new file mode 100755 index 000000000000..69132ae2eea6 Binary files /dev/null and b/docs/images/DeleteCardSdForLogic.png differ diff --git a/docs/images/DeletePersonSdForLogic.png b/docs/images/DeletePersonSdForLogic.png deleted file mode 100644 index 0462b9b7be6e..000000000000 Binary files a/docs/images/DeletePersonSdForLogic.png and /dev/null differ diff --git a/docs/images/EndCommandPage.png b/docs/images/EndCommandPage.png new file mode 100755 index 000000000000..9f768d3b3704 Binary files /dev/null and b/docs/images/EndCommandPage.png differ diff --git a/docs/images/EnterFolderGUISequenceDiagram.png b/docs/images/EnterFolderGUISequenceDiagram.png new file mode 100644 index 000000000000..c76f6d9305fb Binary files /dev/null and b/docs/images/EnterFolderGUISequenceDiagram.png differ diff --git a/docs/images/EnterFolderUI.png b/docs/images/EnterFolderUI.png new file mode 100644 index 000000000000..1756ca6feedb Binary files /dev/null and b/docs/images/EnterFolderUI.png differ diff --git a/docs/images/ExampleMcqTest.png b/docs/images/ExampleMcqTest.png new file mode 100644 index 000000000000..384f39b07824 Binary files /dev/null and b/docs/images/ExampleMcqTest.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png old mode 100644 new mode 100755 index f4ecf65b3193..3660616142df Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/Logo.png b/docs/images/Logo.png new file mode 100644 index 000000000000..b7b712b2c916 Binary files /dev/null and b/docs/images/Logo.png differ diff --git a/docs/images/ModelClassBetterOopDiagram.png b/docs/images/ModelClassBetterOopDiagram.png index b7df3a1c02b4..537465f42bed 100644 Binary files a/docs/images/ModelClassBetterOopDiagram.png and b/docs/images/ModelClassBetterOopDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 4961edd74e76..bb69273fd039 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/ModelEnhancementDiagram.png b/docs/images/ModelEnhancementDiagram.png new file mode 100644 index 000000000000..f2a081df3ec8 Binary files /dev/null and b/docs/images/ModelEnhancementDiagram.png differ diff --git a/docs/images/NextCommandPage.png b/docs/images/NextCommandPage.png new file mode 100755 index 000000000000..9173b3ba1ad9 Binary files /dev/null and b/docs/images/NextCommandPage.png differ diff --git a/docs/images/NextCommandSequenceDiagram.png b/docs/images/NextCommandSequenceDiagram.png new file mode 100755 index 000000000000..cb2929e5793e Binary files /dev/null and b/docs/images/NextCommandSequenceDiagram.png differ diff --git a/docs/images/ReportCommandSequenceDiagram.png b/docs/images/ReportCommandSequenceDiagram.png new file mode 100644 index 000000000000..15a14c23615f Binary files /dev/null and b/docs/images/ReportCommandSequenceDiagram.png differ diff --git a/docs/images/ReportDisplay.png b/docs/images/ReportDisplay.png new file mode 100644 index 000000000000..945241b81c55 Binary files /dev/null and b/docs/images/ReportDisplay.png differ diff --git a/docs/images/ReportDisplaySequenceDiagram.png b/docs/images/ReportDisplaySequenceDiagram.png new file mode 100644 index 000000000000..c9e45743ba0a Binary files /dev/null and b/docs/images/ReportDisplaySequenceDiagram.png differ diff --git a/docs/images/RevealAnswerPage.png b/docs/images/RevealAnswerPage.png new file mode 100755 index 000000000000..d80ed2b3300f Binary files /dev/null and b/docs/images/RevealAnswerPage.png differ diff --git a/docs/images/SDForDeleteCard.png b/docs/images/SDForDeleteCard.png new file mode 100755 index 000000000000..20dd45291d2e Binary files /dev/null and b/docs/images/SDForDeleteCard.png differ diff --git a/docs/images/SDforDeletePerson.png b/docs/images/SDforDeletePerson.png deleted file mode 100644 index ae171fda7622..000000000000 Binary files a/docs/images/SDforDeletePerson.png and /dev/null differ diff --git a/docs/images/StartupUI.png b/docs/images/StartupUI.png new file mode 100644 index 000000000000..ff1ac9376228 Binary files /dev/null and b/docs/images/StartupUI.png differ diff --git a/docs/images/StatusBarInFolder.png b/docs/images/StatusBarInFolder.png new file mode 100644 index 000000000000..bf831e0f1000 Binary files /dev/null and b/docs/images/StatusBarInFolder.png differ diff --git a/docs/images/StatusBarInHomeDirectory.png b/docs/images/StatusBarInHomeDirectory.png new file mode 100644 index 000000000000..cde2bff26cba Binary files /dev/null and b/docs/images/StatusBarInHomeDirectory.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index e5527ecac459..8262d603c8c2 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/StorageEnhancementDiagram.png b/docs/images/StorageEnhancementDiagram.png new file mode 100644 index 000000000000..8a216861df29 Binary files /dev/null and b/docs/images/StorageEnhancementDiagram.png differ diff --git a/docs/images/TestCommandSequenceDiagram.png b/docs/images/TestCommandSequenceDiagram.png new file mode 100755 index 000000000000..dfe2895bd91f Binary files /dev/null and b/docs/images/TestCommandSequenceDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5ec9c527b49c..a5c5efbbde97 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 5f3847621e07..36c6f4d58de7 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UndoRedoActivityDiagram.png b/docs/images/UndoRedoActivityDiagram.png index 55e4138cc64f..2228da14dec3 100644 Binary files a/docs/images/UndoRedoActivityDiagram.png and b/docs/images/UndoRedoActivityDiagram.png differ diff --git a/docs/images/UndoRedoExecuteUndoStateListDiagram.png b/docs/images/UndoRedoExecuteUndoStateListDiagram.png index 29c365d6b4a1..6ff01cf32f2e 100644 Binary files a/docs/images/UndoRedoExecuteUndoStateListDiagram.png and b/docs/images/UndoRedoExecuteUndoStateListDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand2StateListDiagram.png b/docs/images/UndoRedoNewCommand2StateListDiagram.png index adcb9aeadc51..e5ad1e8c2b12 100644 Binary files a/docs/images/UndoRedoNewCommand2StateListDiagram.png and b/docs/images/UndoRedoNewCommand2StateListDiagram.png differ diff --git a/docs/images/UndoRedoSequenceDiagram.png b/docs/images/UndoRedoSequenceDiagram.png index 5c9d5936f098..00052c3f9b9e 100644 Binary files a/docs/images/UndoRedoSequenceDiagram.png and b/docs/images/UndoRedoSequenceDiagram.png differ diff --git a/docs/images/WrongAnswerPage.png b/docs/images/WrongAnswerPage.png new file mode 100755 index 000000000000..e751e06bebad Binary files /dev/null and b/docs/images/WrongAnswerPage.png differ diff --git a/docs/images/afterdusk.png b/docs/images/afterdusk.png new file mode 100755 index 000000000000..8f229bd7f588 Binary files /dev/null and b/docs/images/afterdusk.png differ diff --git a/docs/images/award1.png b/docs/images/award1.png new file mode 100644 index 000000000000..11e74390f3dc Binary files /dev/null and b/docs/images/award1.png differ diff --git a/docs/images/award2.png b/docs/images/award2.png new file mode 100644 index 000000000000..dbc03a2b00ea Binary files /dev/null and b/docs/images/award2.png differ diff --git a/docs/images/damithc.jpg b/docs/images/damithc.jpg deleted file mode 100644 index 127543883893..000000000000 Binary files a/docs/images/damithc.jpg and /dev/null differ diff --git a/docs/images/dlqs.png b/docs/images/dlqs.png new file mode 100755 index 000000000000..46fe0ab5bffc Binary files /dev/null and b/docs/images/dlqs.png differ diff --git a/docs/images/export_model_list.png b/docs/images/export_model_list.png new file mode 100644 index 000000000000..271ce1297779 Binary files /dev/null and b/docs/images/export_model_list.png differ diff --git a/docs/images/import_export_model_list1.png b/docs/images/import_export_model_list1.png new file mode 100644 index 000000000000..4c6aab21c743 Binary files /dev/null and b/docs/images/import_export_model_list1.png differ diff --git a/docs/images/import_export_sequenceDiagram.png b/docs/images/import_export_sequenceDiagram.png new file mode 100644 index 000000000000..d39179b34ee4 Binary files /dev/null and b/docs/images/import_export_sequenceDiagram.png differ diff --git a/docs/images/inFolderUiPPP.png b/docs/images/inFolderUiPPP.png new file mode 100644 index 000000000000..4a457d65459c Binary files /dev/null and b/docs/images/inFolderUiPPP.png differ diff --git a/docs/images/inHomeUiPPP.png b/docs/images/inHomeUiPPP.png new file mode 100644 index 000000000000..2cc299fdd2cd Binary files /dev/null and b/docs/images/inHomeUiPPP.png differ diff --git a/docs/images/kerryneer.png b/docs/images/kerryneer.png new file mode 100755 index 000000000000..047f239722b0 Binary files /dev/null and b/docs/images/kerryneer.png differ diff --git a/docs/images/lejolly.jpg b/docs/images/lejolly.jpg deleted file mode 100644 index 2d1d94e0cf5d..000000000000 Binary files a/docs/images/lejolly.jpg and /dev/null differ diff --git a/docs/images/m133225.jpg b/docs/images/m133225.jpg deleted file mode 100644 index fd14fb94593a..000000000000 Binary files a/docs/images/m133225.jpg and /dev/null differ diff --git a/docs/images/mcqCard.PNG b/docs/images/mcqCard.PNG new file mode 100644 index 000000000000..7d6855d1b675 Binary files /dev/null and b/docs/images/mcqCard.PNG differ diff --git a/docs/images/mmdlow.png b/docs/images/mmdlow.png new file mode 100755 index 000000000000..78a5075a41a8 Binary files /dev/null and b/docs/images/mmdlow.png differ diff --git a/docs/images/project_root_dir.png b/docs/images/project_root_dir.png new file mode 100644 index 000000000000..17d0ddfdae7b Binary files /dev/null and b/docs/images/project_root_dir.png differ diff --git a/docs/images/singleAnswerCard.PNG b/docs/images/singleAnswerCard.PNG new file mode 100644 index 000000000000..eda636afc5ed Binary files /dev/null and b/docs/images/singleAnswerCard.PNG differ diff --git a/docs/images/startTestSessionPage.png b/docs/images/startTestSessionPage.png new file mode 100755 index 000000000000..86b05e84f6ef Binary files /dev/null and b/docs/images/startTestSessionPage.png differ diff --git a/docs/images/yichong96.png b/docs/images/yichong96.png new file mode 100644 index 000000000000..fa2458ee7f1c Binary files /dev/null and b/docs/images/yichong96.png differ diff --git a/docs/images/yijinl.jpg b/docs/images/yijinl.jpg deleted file mode 100644 index adbf62ad9406..000000000000 Binary files a/docs/images/yijinl.jpg and /dev/null differ diff --git a/docs/images/yl_coder.jpg b/docs/images/yl_coder.jpg deleted file mode 100644 index 17b48a732272..000000000000 Binary files a/docs/images/yl_coder.jpg and /dev/null differ diff --git a/docs/stylesheets/asciidoctor.css b/docs/stylesheets/asciidoctor.css index 36590bf346cd..579d2f7c8485 100644 --- a/docs/stylesheets/asciidoctor.css +++ b/docs/stylesheets/asciidoctor.css @@ -1,6 +1,6 @@ /* 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";*/ +/*@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CRoboto: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} @@ -42,7 +42,7 @@ 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} +body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Roboto","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%} @@ -181,7 +181,7 @@ body.toc2.toc-right{padding-left:0;padding-right:20em}} #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} +.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:"Roboto","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} diff --git a/docs/team/afterdusk.adoc b/docs/team/afterdusk.adoc new file mode 100644 index 000000000000..5f54f0fdbf4d --- /dev/null +++ b/docs/team/afterdusk.adoc @@ -0,0 +1,105 @@ += Au Liang Jun - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== image:Logo.png[width=50] Know-It-All +:icons: font + +== About the Project + +Know-It-All is a flashcard application borne from CS2103T, a software engineering course spanning 13 weeks. We were tasked to take an existing addressbook application and either enhance it or morph it into a different product. After 4 developmental milestones, the result is Know-It-All, a digital flashcard solution that helps users store and organise their learning material. With an easy to use interface and convenient sharing functionality, Know-It-All iss designed to help students perform rote learning more efficiently. From cramming in between lessons to focused study, the interactive test session boosts the effectiveness of repetition and recall for memorisation. Download Know-It-All https://github.com/cs2103-ay1819s2-w10-4/main/releases[here]! + +My role in the project was to implement a folder system, so that users could group together multiple sets of flashcards. Apart from writing code, I maintained the User Guide, Developer Guide and project GitHub repository. Most importantly, I synergised with my team members to work productively and help everyone achieve their goals. + +In this document, the following style elements from the respective documentation are present: + +`test`: Command to be executed or a component, class or object in the architecture of the application. + +[NOTE] +Important information that should be noted. + +[TIP] +Useful tips that can help if you get stuck. + +===== +Useful information for a deeper understanding of the current topic. +===== + +== Summary of Contributions +*Major Enhancement*: I added the ability to have *flashcard folders* + +* What it does: allows the user to create folders that store separate sets of flashcards. Folders can be renamed and deleted. +* Justification: This feature allows the user to logically group flashcards of the same topic together, separate from flashcards of other topics. This significantly improves the product as it enables better organisation, which is crucial for efficiency and ease of use. +* Highlights: The enhancement affected all components extensively. For some components, the architecture had to be redesigned, and the multiple options to do so had to be carefully considered and assessed. The implementation was also challenging due to the tightly coupled nature of classes within each component, and the non-trivial changes to architecture required both a deep and broad understanding of how every component worked. + +*Code Contributed*: Please click https://nus-cs2103-ay1819s2.github.io/cs2103-dashboard/#search=afterdusk[here] to see samples of my functional and test code. + +** The tests I wrote increased coverage from 80% to 84% (Pull requests https://github.com/cs2103-ay1819s2-w10-4/main/pull/104[#104], https://github.com/cs2103-ay1819s2-w10-4/main/pull/177[#177]) + +*Other Contributions*: + +* Project Management: +** Created GitHub organisation and team repository, issue labels, milestones and https://github.com/cs2103-ay1819s2-w10-4/main/projects/1[project board] +** Managed and tagged milestones `v1.1` to `v1.2` + +* Documentation: +** User Guide: +*** Introduced an About section (later renamed to How To Use this Guide) and populated the Command Summary and Glossary sections. Also revised the document format be more consistent (Pull request https://github.com/cs2103-ay1819s2-w10-4/main/pull/90[#90]) +*** Rewrote and added annotated UI images to Quick Start to improve clarity (Pull request https://github.com/cs2103-ay1819s2-w10-4/main/pull/180[#180]) + +** Developer Guide: +*** Wrote user stories to set project requirements, which were eventually ported over to GitHub issues for project management (Pull request https://github.com/cs2103-ay1819s2-w10-4/main/pull/53[#53]) +*** Contributed to use cases and manual testing (Pull request https://github.com/cs2103-ay1819s2-w10-4/main/pull/104[#104], https://github.com/cs2103-ay1819s2-w10-4/main/pull/180[#180]) + +* Community: +** Performed the initial refactoring of code to flashcard context (Pull requests https://github.com/cs2103-ay1819s2-w10-4/main/pull/37[#37]) +** Reviewed over 20 pull requests, spotting potential errors and making suggestions (Pull requests https://github.com/cs2103-ay1819s2-w10-4/main/pull/128[#128], https://github.com/cs2103-ay1819s2-w10-4/main/pull/178[#178]) +** Made bug reports to notify teammates of bugs related to their components (Issues https://github.com/cs2103-ay1819s2-w10-4/main/issues/118[#118], https://github.com/cs2103-ay1819s2-w10-4/main/issues/122[#122], https://github.com/cs2103-ay1819s2-w10-4/main/issues/136[#136], https://github.com/cs2103-ay1819s2-w10-4/main/issues/169[#169]) and addressed/assigned bug reports from external testers +** Integrated a GitHub plugin (Netlify) to the team repo + +== Contributions to the User Guide + + +===== +_As we morphed the product, we kept the user guide up to date with the features we implemented. To showcase my technical writing skills, I have included an excerpt of my notable contributions, namely Quick Start and the new folder-related commands._ +===== + +include::../UserGuide.adoc[tag=introabout] + +include::../UserGuide.adoc[tag=quickstart] + +include::../UserGuide.adoc[tag=folderoperations] + +include::../UserGuide.adoc[tag=cmdsummary] + +== Contributions to the Developer Guide + +// ===== +// _We also kept the Developer Guide updated so that potential developers and enthusiast users could better understand the design and implementation of our app. To show the technical depth of my contributions while keeping this document brief, I have once again only included the more notable excerpts._ +// ===== + +===== +_We also kept the Developer Guide updated so that potential developers and enthusiast users could better understand the design and implementation of our app. This section is long-running, although the first few pages of the section should suffice in showcasing the technical depth of my contributions._ +===== + +include::../DeveloperGuide.adoc[tag=modeldesign] + +include::../DeveloperGuide.adoc[tag=folders] + +include::../DeveloperGuide.adoc[tag=folderusecases] + +include::../DeveloperGuide.adoc[tag=foldermanualtesting] + +// == Additional Contributions to the User Guide + +// ===== +// _This section is GitHub only (not in PDF copy of PPP). They cover my contributions to the User Guide more extensively._ +// ===== + +// include::../UserGuide.adoc[tag=introabout] + +// include::../UserGuide.adoc[tag=quickstart] + +// include::../UserGuide.adoc[tag=folderoperations] + +// include::../UserGuide.adoc[tag=cmdsummary] diff --git a/docs/team/dlqs.adoc b/docs/team/dlqs.adoc new file mode 100644 index 000000000000..b2e9856d4e1c --- /dev/null +++ b/docs/team/dlqs.adoc @@ -0,0 +1,82 @@ += Donald Lee - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Know-It-All + +--- + +== Overview + +Know-It-All is a flashcard application that helps students manage, store, and use flash cards effectively. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. +My major roles included the report feature and ability to record score. To see directly what code I contributed, a https://nus-cs2103-ay1819s2.github.io/cs2103-dashboard/#search=dlqs[summary] provided by RepoSense is available here. + +== Summary of contributions +|=== +|_Below is a summary of my coding, documentation, and other helpful contributions to the team project._ +|=== + +* *Major enhancement*: added the *ability to generate test scoring report for card folder* +** What it does: Records the number of questions correctly answered after a test session, called the test score, and generates a report for that folder that shows the change in (past) test scores. +There is also a line showing percentage change in test score as well as the lowest individual scoring cards in the current folder. + Test scores are only recorded if at least 1/4 of the questions in the folder are attempted. An example is shown below. + +** image:ReportDisplay.png[width="790"] + +** Justification: This feature improves the product significantly because a user can see the outcome of the spaced-repetition technique employed by Know-It-All. They are able to track their average score over time, per folder, so that +they are able tell which folders they score well and not so well for. This lets them know which folder they need to practice more on. + Also, the minimum number of questions required to be answered is so that the user do not get their accidental test attempts recorded. +** Highlights: This enhancement has a graph showing a maximum of the 10 test session scores. The color of the folder score change +also changes based on whether it was a positive, same or negative change. + +* *Major enhancement*: added *the ability to sort by scoring performance* +** What it does: allows the user to sort the cards by scoring performance. Other features can make use of this feature by using any appropriate comparator. +** Justification: This lets the user see which cards they scored the lowest on, so that they know which cards require more practice. +This feature also improves the product significantly because many other features can be built on top of this one, e.g. the test session, which puts the lowest scoring cards in front. +** Highlights: This enhancement affects the existing list. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changing the way cards are naturally ordered. + +* *Minor enhancement*: Added State to Model Component's to represent user's "location" in the application. This removes edge cases of commands being executed illegally, i.e. where they otherwise shouldn't be. + +* *Minor enhancement*: added a score attribute that allows the application to track the number of correct and incorrect attempts for individual cards. + +* *Code contributed*: [https://github.com/cs2103-ay1819s2-w10-4/main/pull/75[ability to sort]] [https://github.com/cs2103-ay1819s2-w10-4/main/pull/46[add score attribute]][https://github.com/cs2103-ay1819s2-w10-4/main/pull/97[add report]] + +* *Other contributions*: + +** Enhancements to existing features: +*** Wrote additional unit tests for new features, as well as system tests for Test and Report features, to increase coverage from 80% to 87% (Pull requests https://github.com/cs2103-ay1819s2-w10-4/main/pull/75[#75], https://github.com/cs2103-ay1819s2-w10-4/main/pull/167[#167]) + +** Documentation: +*** Rewrote the project README to look like a real product: https://github.com/cs2103-ay1819s2-w10-4/main/pull/107[#107] +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com/cs2103-ay1819s2-w10-4/main/pull/57[#57], https://github.com/cs2103-ay1819s2-w10-4/main/pull/49[#49], https://github.com/cs2103-ay1819s2-w10-4/main/pull/72[#72], https://github.com/cs2103-ay1819s2-w10-4/main/pull/129[#129] +** Tools: +*** Integrated Github plugins Travis CI, AppVeyor and Coveralls to the team repo: https://github.com/cs2103-ay1819s2-w10-4/main/pull/2[#2] + +== 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._ +|=== + +I implemented a new report command as well as sort command, which required updates to the user guide so that new users +know how to use it. + +include::../UserGuide.adoc[tag=reportoperations] + +include::../UserGuide.adoc[tag=sortcommand] + +== 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._ +|=== + +Besides the aforementioned new features for users, I also refactored internal code which affects how other developers should contribute +to the project, namely how to pass results from Model into Ui and Model state that the user is in. + +include::../DeveloperGuide.adoc[tag=report] +include::../DeveloperGuide.adoc[tag=state] +=== Manual testing +include::../DeveloperGuide.adoc[tag=reportmanualtest] +include::../DeveloperGuide.adoc[tag=sortmanualtest] +include::../DeveloperGuide.adoc[tag=reportusecase] diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc deleted file mode 100644 index 453c2152ab9d..000000000000 --- a/docs/team/johndoe.adoc +++ /dev/null @@ -1,72 +0,0 @@ -= John Doe - Project Portfolio -:site-section: AboutUs -:imagesDir: ../images -:stylesDir: ../stylesheets - -== PROJECT: AddressBook - Level 4 - ---- - -== Overview - -AddressBook - Level 4 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -== Summary of contributions - -* *Major enhancement*: added *the ability to undo/redo previous commands* -** What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. -** Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. -** Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. -** Credits: _{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}_ - -* *Minor enhancement*: added a history command that allows the user to navigate to previous commands using up/down keys. - -* *Code contributed*: [https://github.com[Functional code]] [https://github.com[Test code]] _{give links to collated code files}_ - -* *Other contributions*: - -** Project management: -*** Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub -** Enhancements to existing features: -*** Updated the GUI color scheme (Pull requests https://github.com[#33], https://github.com[#34]) -*** Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests https://github.com[#36], https://github.com[#38]) -** Documentation: -*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com[#14] -** Community: -*** PRs reviewed (with non-trivial review comments): https://github.com[#12], https://github.com[#32], https://github.com[#19], https://github.com[#42] -*** Contributed to forum discussions (examples: https://github.com[1], https://github.com[2], https://github.com[3], https://github.com[4]) -*** Reported bugs and suggestions for other teams in the class (examples: https://github.com[1], https://github.com[2], https://github.com[3]) -*** Some parts of the history feature I added was adopted by several other class mates (https://github.com[1], https://github.com[2]) -** Tools: -*** Integrated a third party library (Natty) to the project (https://github.com[#42]) -*** Integrated a new Github plugin (CircleCI) to the team repo - -_{you can add/remove categories in the list above}_ - -== Contributions to the User Guide - - -|=== -|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ -|=== - -include::../UserGuide.adoc[tag=undoredo] - -include::../UserGuide.adoc[tag=dataencryption] - -== Contributions to the Developer Guide - -|=== -|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ -|=== - -include::../DeveloperGuide.adoc[tag=undoredo] - -include::../DeveloperGuide.adoc[tag=dataencryption] - - -== PROJECT: PowerPointLabs - ---- - -_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/kerryneer.adoc b/docs/team/kerryneer.adoc new file mode 100755 index 000000000000..1e2ee0eeb2e3 --- /dev/null +++ b/docs/team/kerryneer.adoc @@ -0,0 +1,107 @@ += Kerryn Eer - Project Portfolio for Know-It-All +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets +ifdef::env-github[] +:note-caption: :information_source: +endif::[] + +== About the project + +I worked in a team of 5 for a software engineering project to morph a basic command line interface addressbook into a +flashcard management application called Know-It-All. Know-It-All is designed to help medical students store, organise +and share their learning material, integrated with a test session feature and scoring system to allow for a more efficient rote learning process. + +My role was to design and implement the test session feature. The following sections illustrate this major feature in more detail. + +Note the following icons and formatting used in this document: + +NOTE: This symbol indicates important information related to this section. + +`test`: A grey highlight (called a mark-up) indicates that this is a command to be executed or a component, class +or object in the architecture of the application. + +==== +Information in a box like this represents additional useful information related to this section. +==== + + +== Summary of contributions +This section shows a summary of my coding, documentation, and other helpful contributions to the team project. + +*Major feature*: I implemented the functionalities of a test session. + +* What it does: This allows users to begin and end a test session. While inside a test session, flashcard questions +will be displayed, and student can input an answer or choose to reveal the answer before moving on to the next +flashcard. +* Justification: This feature is the main highlight of Know-It-All, as students use flashcards to test how +much content they have memorised. Equipped with the user-friendly answer command that is missing in existing +flashcard applications, Know-It-All provides a more engaging and interactive testing experience for the user. +* Highlights: This feature modifies the existing UI to facilitate the addition of a new test session screen. It also +has a heavy dependency on folder methods. Hence, this feature required an in-depth analysis of design alternatives, +considering design patterns and principles as well as the user-friendliness of the commands. +More details can be found under design considerations sections of <>. +Implementation and writing tests were also tedious as many checks of the current state of the test session needs to be +done to permit only certain commands and ban others. + +*Code contributed*: [https://nus-cs2103-ay1819s2.github.io/cs2103-dashboard/#search=kerryneer[Collated code]] + +*Other contributions*: + +* Enhancements to features: +** Wrote additional tests for new features to increase coverage from 79% to 81% (Pull requests https://github.com/cs2103-ay1819s2-w10-4/main/pull/74[#74]) +** Refactored existing status bar to display the current state of the user (whether user is in a test session, in a +report display, in a folder or in home directory) instead of the previous last saved location and time. Corresponding + tests are written as well. (Pull requests https://github.com/cs2103-ay1819s2-w10-4/main/pull/162[#162]) +** Updated UI colour scheme to enhance aesthetics and clarity of Know-It-All (Pull requests https://github.com/cs2103-ay1819s2-w10-4/main/pull/162[#162], https://github.com/cs2103-ay1819s2-w10-4/main/pull/166[#166], +https://github.com/cs2103-ay1819s2-w10-4/main/pull/174[#174]) + +* Documentation: +** User guide: I updated the QuickStart section, added new features, remove previous addressbook features and modify +existing commands (Pull requests https://github.com/cs2103-ay1819s2-w10-4/main/pull/60[#60], https://github.com/cs2103-ay1819s2-w10-4/main/pull/59[#59], https://github.com/cs2103-ay1819s2-w10-4/main/pull/80[#80], +https://github.com/cs2103-ay1819s2-w10-4/main/pull/106[#106], https://github.com/cs2103-ay1819s2-w10-4/main/pull/127[#127]) +** Developer guide: I updated relevant sections related to addressbook to our Know-It-All flashcard context +like Product scope and Non-Functional Requirements, some of its class diagrams and sequence diagrams, and added +implementation for test session (Pull requests https://github.com/cs2103-ay1819s2-w10-4/main/pull/80[#80], +https://github.com/cs2103-ay1819s2-w10-4/main/pull/106[#106]) +** About Us page: I collated and updated profiles for all members of the team. (Pull requests https://github.com/cs2103-ay1819s2-w10-4/main/pull/6[#6], +https://github.com/cs2103-ay1819s2-w10-4/main/pull/127[#127]) + +* Community: +** PRs reviewed with non-trivial review comments: +https://github.com/cs2103-ay1819s2-w10-4/main/pull/65[#65] (suggested another method of implementation), +https://github.com/cs2103-ay1819s2-w10-4/main/pull/46[#46] (explained why Travis CI was failing) +** Conducted manual testing, reported bugs and offered suggestions for our team: https://github.com/cs2103-ay1819s2-w10-4/main/issues/119[#119], https://github.com/cs2103-ay1819s2-w10-4/main/issues/123[#123], https://github.com/cs2103-ay1819s2-w10-4/main/issues/124[#124], +https://github.com/cs2103-ay1819s2-w10-4/main/issues/125[#125], https://github.com/cs2103-ay1819s2-w10-4/main/issues/134[#134] +** Conducted manual testing and reported bugs for other teams in class: https://github.com/CS2103-AY1819S2-W14-1/main/issues/170[#170], https://github.com/CS2103-AY1819S2-W14-1/main/issues/158[#158], +https://github.com/CS2103-AY1819S2-W14-1/main/issues/165[#165], https://github.com/CS2103-AY1819S2-W14-1/main/issues/145[#145] + +== Contributions to the User Guide +We had to update the original addressbook User Guide with instructions for Know-It-All enhancements that we +had added. The following is an excerpt from our updated Know-It-All User Guide, showing a few of the notable additions that I have made for the test session feature. + +=== Command Format +* Words in `UPPER_CASE` are the parameters to be supplied by you e.g. in `addfolder FOLDER_NAME`, +`FOLDER_NAME` is a parameter which can be something like `Human Anatomy`. + +* Items in square brackets are optional e.g `HINTS` in `add q/QUESTION a/ANSWER [h/HINTS]`. + + +include::../UserGuide.adoc[tag=testoperations] + +[[contributionstodevguide]] +== Contributions to the Developer Guide +Besides updating the original addressbook User Guide, Developer Guide has to be updated to Know-It-All context and +features as well. To keep this section concise, I have included an excerpt from my contributions to the updated Know-It-All Developer Guide about the UI Component and the implementation details and design considerations for the test session feature. + +include::../DeveloperGuide.adoc[tag=UIdesign] + +include::../DeveloperGuide.adoc[tag=testcommand] + +include::../DeveloperGuide.adoc[tag=testdesign] + +=== Use Cases +include::../DeveloperGuide.adoc[tag=testsessionusecases] + +=== Instructions for Manual Testing +include::../DeveloperGuide.adoc[tag=testmanualtesting] diff --git a/docs/team/mmdlow.adoc b/docs/team/mmdlow.adoc new file mode 100644 index 000000000000..87c02bc5cc1d --- /dev/null +++ b/docs/team/mmdlow.adoc @@ -0,0 +1,107 @@ += Matthew Low - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Know-It-All + +--- + +== Overview + +Know-It-All is a flashcard application developed as a software engineering project by a team of 5, including myself. The base application was originally an addressbook application, and my team was given the choice of either enhancing its current features, or morphing the application to suit a different purpose. We chose the latter path, which resulted in the current iteration of Know-It-All. + +Our application aims to assist students in their learning by proving them with an easy-to-use digital flashcard management system to create and review their own flashcards. Through components such as the in-built test session and report feature, users can learn more effectively and review their performance. Interaction is primarily done via a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +My role involved the restructuring of the `model` and `logic` components of the original addressbook application, as well as implementing flashcard creation and management for Know-It-All. A more detailed breakdown of my contributions are detailed below, together with my additions to the user and developer guides. + +Lookout for the following icons and formatting used throughout the document for your reference: + +[NOTE] +Important information that should be noted. + +[TIP] +Useful tips that can help if you get stuck. + +`test`: Command to be executed or less commonly, a component, class or object in the architecture of the application. + +==== +Useful information for a deeper understanding of the command. +==== + +== Summary of Contributions + +|=== +|_The section below shows a summary of my coding, documentation and other helpful contributions to the team project._ +|=== + +* *Major enhancement*: I implemented *flashcard creation and management* +** What it does: Allows users to create, edit and delete different kinds of flashcards. +** Justification: This is a core feature of the project, as users will be spending a large portion of their time interacting with the flashcards they have created or imported. +** Highlights: +*** Users can create 2 different types of flashcards, Single-answer cards and MCQ cards. This provides greater versatility in how users choose to structure their learning material through the flashcards. +*** Users can easily convert between both types of cards by simply adding or removing MCQ options. +*** Users have the option to include a hint for each flashcard. + +* *Minor enhancement*: I implemented *testing for MCQ cards* +** What it does: For MCQ card tests, users can input a number corresponding to the options available for an MCQ card, instead of having to input the entire answer. +** Justification: This provides greater flexibility in the way users can interact with test sessions. It also reduces the need for users to type in long answers for MCQ questions, where the risk of spelling errors might needlessly impact their score for the card. +** Highlights: MCQ card options are randomized every time the card is tested, thus preventing users from simply memorizing the correct option number. + +* *Minor enhancement*: I updated *UI display for cards within card folders and for tests* +** What it does: Changed the display of the side panel for cards within card folders to display all card information to the user. +** Justification: +*** For card folders, the side panel shows users all necessary information of a card at a glance whenever users select a card. +*** For test sessions, only relevant information is shown at the start for each card. The correct answer is displayed only after a user inputs an answer. + +** Highlights: +*** In a test session, the card color changes to reflect if a user has answered correctly or not. Green signifies a correct answer, while red signifies a wrong answer. ++ +image:ExampleMcqTest.png[width="250"] + +* *Code contributed*: An overview of my contributed code to the project can be found https://nus-cs2103-ay1819s2.github.io/cs2103-dashboard/#search=mmdlow[here]. + +* *Other contributions*: + +** Enhancements to existing features: +*** Wrote additional tests for new and existing features: https://github.com/cs2103-ay1819s2-w10-4/main/pull/100[#100], https://github.com/cs2103-ay1819s2-w10-4/main/pull/110[#110], https://github.com/cs2103-ay1819s2-w10-4/main/pull/178[#178], https://github.com/cs2103-ay1819s2-w10-4/main/pull/185[#185] + +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com/cs2103-ay1819s2-w10-4/main/pull/54[#54], https://github.com/cs2103-ay1819s2-w10-4/main/pull/73[#73], https://github.com/cs2103-ay1819s2-w10-4/main/pull/142[#142] +*** Reported bugs and issues involving our team's application: https://github.com/cs2103-ay1819s2-w10-4/main/issues/184[#184], https://github.com/cs2103-ay1819s2-w10-4/main/issues/188[#188] +*** Reported bugs and issues involving other teams' applications: https://github.com/CS2103-AY1819S2-W15-2/main/issues/248[#248], https://github.com/CS2103-AY1819S2-W15-2/main/issues/253[#253] + +[[contributionsToUserGuide]] +== Contributions to the User Guide + + +|=== +|_As development of Know-It-All progressed, our team had to concurrently update the User Guide to reflect the changes and new features that we implemented. The section below lists several key excerpts from the User Guide that I contributed to, and showcases my ability to write technical documentation. These include the sections on adding cards and editing cards._ +|=== + +[[add]] +include::../UserGuide.adoc[tag=add] + +[[edit]] +include::../UserGuide.adoc[tag=edit] + +== Contributions to the Developer Guide + + +|=== +|_Our team also updated the Developer Guide to reflect the new changes and enhancements made for Know-It-All. As with the section on <>, some key excerpts of my contributions to the Developer Guide are detailed below. These include sections on <>, <>, the <>, and <>._ +|=== + +[[cards]] +include::../DeveloperGuide.adoc[tag=cards] + +[[cardUseCases]] +=== Card Use Cases +include::../DeveloperGuide.adoc[tag=cardusecases] + +[[glossary]] +include::../DeveloperGuide.adoc[tag=glossary] + +[[cardManualTesting]] +=== Instructions on Manual Testing for Cards +include::../DeveloperGuide.adoc[tag=cardmanualtesting] diff --git a/docs/team/yichong96.adoc b/docs/team/yichong96.adoc new file mode 100644 index 000000000000..e462d86728de --- /dev/null +++ b/docs/team/yichong96.adoc @@ -0,0 +1,99 @@ += Ong Yi Chong - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Know-It-All +--- + +Hello ! I am Yi Chong, a year 2 Computer Science student. I have worked on a few projects in the past, of which I have +developed an android application and built a machine learning model to classify e-commerce products. My current areas of interest +include Artificial Intelligence (AI) and Software Engineering. This project portfolio serves to document my contributions +to Know-It-All; including the code base, developer and user guide. + +== Overview +The motivation behind this project came from a problem faced by Wei Jie, a medical student and friend of my group mate. +Wei Jie is an avid user of flashcards for study. However, the trouble of carrying physical flashcards and +the sheer amount of content recorded on flashcards has caused major inconveniences to Wei Jie. + +As part of our Software Engineering project, we had the option to either enhance the existing address book software or +morph it into another product. As such, our team decided to morph the existing product into a flashcard application for +students like Wei Jie, who prefer flashcards as a study method but find using physical flashcards a major pain point. + +This enhanced application helps students organize their digital flashcards neatly. +The application also comes with a test feature; allowing users to test themselves on the flashcards +created, a report feature; allowing them to view their performance for each folder and a import and +export feature; which allows for the sharing of flashcards. + +My role was to design and write the codes for the import and export features. The following sections +illustrate these enhancements in more detail, as well as the relevant sections I have added to the +user and developer guides in relation to these enhancements. + + + +=== Legend +NOTE: This admonition contains important information pertaining to the usage of the commands. + +`command`: A monospace font has two meanings. For the user guide, it represents a command +that can be inputted into the command line and executed by the application. In the Developer guide, +it can represent methods, variables or classes found within the Java files in addition to command line +input by the user. + + +== Summary of contributions +This section contains a summary of my contributions to the project; +mainly the code and documentation for the import and export feature as well as contributions to the community. + +* *Major enhancement*: added *the ability to import/export existing or new flashcards* +** What it does: The import command allows users to import flashcards from a a .csv file while the export command +allows users to export flashcards as a .csv file. +** Justification: These two complementary features improve the product significantly because users can now share their flashcards with others, +saving time taken to create similar flashcards for the other user. +Also, being able to import flashcards from a csv file provides users with a quicker alternative to create large numbers of flashcards. +** Highlights: Although not technically challenging, there were many considerations that went into the design of the import/export feature. One of them was deciding +the formatting of csv files. whether a user should be able to export multiple card folders into a single csv. Also, since commas +were used within each card field as well, there was a need to ensure that the comma separated values are actually not +part of the "commas" within the card field itself. + + +* *Code contributed*: Here is the link to the code I contributed [https://nus-cs2103-ay1819s2.github.io/cs2103-dashboard/#search=yichong96[Collated code] + +* *Other contributions*: +** Enhancements to existing features: +*** Wrote additional tests for existing features to increase coverage from 78.379% to 81.079 (Pull requests https://github.com[#105]) +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com/cs2103-ay1819s2-w10-4/main/pull/60[#60], https://github.com/cs2103-ay1819s2-w10-4/main/pull/2[#2] +*** Conducted manual testing for other team's projects. Reported bugs and gave suggestions on how to improve their product +: https://github.com/nus-cs2103-AY1819S2/pe-dry-run/issues/903[#903], +https://github.com/nus-cs2103-AY1819S2/pe-dry-run/issues/816[#816], https://github.com/nus-cs2103-AY1819S2/pe-dry-run/issues/772[#772] + + +=== Contributions to the User Guide +--- + +The user guide contains instructions needed to effectively use Know-It-All. +The following is an excerpt from Know-It-All's user guide, which contains the additions that I have made to the user guide. + + +include::../UserGuide.adoc[tag = advancedoperations] + + + +=== Contributions to the Developer Guide +--- + +The following shows my addition to Know-It-All's developer guide for the import and export commmand. + +include::../DeveloperGuide.adoc[tag=importexport] +== Instructions for Manual Testing + +include::../DeveloperGuide.adoc[tag=folderimportexporttesting] + +=== Design Considerations +--- + +Designing the import and export features required me to +make decisions not just in code design, but also to enable users to have a +smooth experience in being able to import and export their flashcards. +. The following is a brief summary of my analysis and decisions. +include::../DeveloperGuide.adoc[tag=design_considerations_import_export] diff --git a/docs/templates/helpers.rb b/docs/templates/helpers.rb index 7060efe223ed..d0d8a9bc508d 100644 --- a/docs/templates/helpers.rb +++ b/docs/templates/helpers.rb @@ -45,7 +45,7 @@ module Slim::Helpers ## # Creates an HTML tag with the given name and optionally attributes. Can take - # a block that will run between the opening and closing tags. + # a block that will run between the opening and closing hints. # # @param name [#to_s] the name of the tag. # @param attributes [Hash] @@ -207,11 +207,11 @@ def html_meta_if(name, content) %() if content end - # Returns formatted style/link and script tags for header. + # Returns formatted style/link and script hints for header. def styles_and_scripts scripts = [] styles = [] - tags = [] + hints = [] stylesheet = attr :stylesheet stylesdir = attr :stylesdir, '' @@ -280,21 +280,21 @@ def styles_and_scripts styles.each do |item| if item.key?(:text) - tags << html_tag(:style, {}, item[:text]) + hints << html_tag(:style, {}, item[:text]) else - tags << html_tag(:link, rel: 'stylesheet', href: urlize(*item[:href])) + hints << html_tag(:link, rel: 'stylesheet', href: urlize(*item[:href])) end end scripts.each do |item| if item.key? :text - tags << html_tag(:script, {type: item[:type]}, item[:text]) + hints << html_tag(:script, {type: item[:type]}, item[:text]) else - tags << html_tag(:script, type: item[:type], src: urlize(*item[:src])) + hints << html_tag(:script, type: item[:type], src: urlize(*item[:src])) end end - tags.join "\n" + hints.join "\n" end end diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2d80b69a7665..d228e2c9d9d1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Fri Mar 01 23:43:27 SGT 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-all.zip diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java deleted file mode 100644 index 1deb3a1e4695..000000000000 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ /dev/null @@ -1,13 +0,0 @@ -package seedu.address.commons.core; - -/** - * Container for user visible messages. - */ -public class Messages { - - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; - public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - -} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java deleted file mode 100644 index 60369e2074e4..000000000000 --- a/src/main/java/seedu/address/logic/Logic.java +++ /dev/null @@ -1,72 +0,0 @@ -package seedu.address.logic; - -import java.nio.file.Path; - -import javafx.beans.property.ReadOnlyProperty; -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 an unmodifiable view of the list of commands entered by the user. - * The list is ordered from the least recent command to the most recent command. - */ - ObservableList getHistory(); - - /** - * 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); - - /** - * Selected person in the filtered person list. - * null if no person is selected. - * - * @see seedu.address.model.Model#selectedPersonProperty() - */ - ReadOnlyProperty selectedPersonProperty(); - - /** - * Sets the selected person in the filtered person list. - * - * @see seedu.address.model.Model#setSelectedPerson(Person) - */ - void setSelectedPerson(Person person); -} 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 5cb24a617beb..000000000000 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ /dev/null @@ -1,108 +0,0 @@ -package seedu.address.logic; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.logging.Logger; - -import javafx.beans.property.ReadOnlyProperty; -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 CommandHistory history; - private final AddressBookParser addressBookParser; - private boolean addressBookModified; - - public LogicManager(Model model, Storage storage) { - this.model = model; - this.storage = storage; - history = new CommandHistory(); - addressBookParser = new AddressBookParser(); - - // Set addressBookModified to true whenever the models' address book is modified. - model.getAddressBook().addListener(observable -> addressBookModified = true); - } - - @Override - public CommandResult execute(String commandText) throws CommandException, ParseException { - logger.info("----------------[USER COMMAND][" + commandText + "]"); - addressBookModified = false; - - CommandResult commandResult; - try { - Command command = addressBookParser.parseCommand(commandText); - commandResult = command.execute(model, history); - } finally { - history.add(commandText); - } - - if (addressBookModified) { - logger.info("Address book modified, saving to file."); - 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 ObservableList getHistory() { - return history.getHistory(); - } - - @Override - public Path getAddressBookFilePath() { - return model.getAddressBookFilePath(); - } - - @Override - public GuiSettings getGuiSettings() { - return model.getGuiSettings(); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - model.setGuiSettings(guiSettings); - } - - @Override - public ReadOnlyProperty selectedPersonProperty() { - return model.selectedPersonProperty(); - } - - @Override - public void setSelectedPerson(Person person) { - model.setSelectedPerson(person); - } -} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index d88e831ff1ce..000000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,69 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; - - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java deleted file mode 100644 index a22219ad76ad..000000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,25 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.logic.CommandHistory; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; - -/** - * Clears the address book. - */ -public class ClearCommand extends Command { - - public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - - - @Override - public CommandResult execute(Model model, CommandHistory history) { - requireNonNull(model); - model.setAddressBook(new AddressBook()); - model.commitAddressBook(); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java deleted file mode 100644 index 92f900b7916d..000000000000 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ /dev/null @@ -1,71 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.Objects; - -/** - * Represents the result of a command execution. - */ -public class CommandResult { - - private final String feedbackToUser; - - /** Help information should be shown to the user. */ - private final boolean showHelp; - - /** The application should exit. */ - private final boolean exit; - - /** - * Constructs a {@code CommandResult} with the specified fields. - */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { - this.feedbackToUser = requireNonNull(feedbackToUser); - this.showHelp = showHelp; - this.exit = exit; - } - - /** - * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, - * and other fields set to their default value. - */ - public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); - } - - public String getFeedbackToUser() { - return feedbackToUser; - } - - public boolean isShowHelp() { - return showHelp; - } - - public boolean isExit() { - return exit; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof CommandResult)) { - return false; - } - - CommandResult otherCommandResult = (CommandResult) other; - return feedbackToUser.equals(otherCommandResult.feedbackToUser) - && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; - } - - @Override - public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); - } - -} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index a20e9d49eac7..000000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,55 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index 952a9e7e7f2b..000000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,228 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -public class EditCommand extends Command { - - public static final String COMMAND_WORD = "edit"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - // state check - EditCommand e = (EditCommand) other; - return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional
getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; - - return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index beb178e3a3f5..000000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,43 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.logic.CommandHistory; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. - */ -public class FindCommand extends Command { - - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 6d44824c7d1b..000000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,25 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.logic.CommandHistory; -import seedu.address.model.Model; - -/** - * Lists all persons in the address book to the user. - */ -public class ListCommand extends Command { - - public static final String COMMAND_WORD = "list"; - - public static final String MESSAGE_SUCCESS = "Listed all persons"; - - - @Override - public CommandResult execute(Model model, CommandHistory history) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java deleted file mode 100644 index baa3c1f30bb4..000000000000 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ /dev/null @@ -1,55 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Selects a person identified using it's displayed index from the address book. - */ -public class SelectCommand extends Command { - - public static final String COMMAND_WORD = "select"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Selects the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Person: %1$s"; - - private final Index targetIndex; - - public SelectCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - - List filteredPersonList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= filteredPersonList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - model.setSelectedPerson(filteredPersonList.get(targetIndex.getZeroBased())); - return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex.getOneBased())); - - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof SelectCommand // instanceof handles nulls - && targetIndex.equals(((SelectCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java deleted file mode 100644 index 40441264f346..000000000000 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; - -/** - * Reverts the {@code model}'s address book to its previous state. - */ -public class UndoCommand extends Command { - - public static final String COMMAND_WORD = "undo"; - public static final String MESSAGE_SUCCESS = "Undo success!"; - public static final String MESSAGE_FAILURE = "No more commands to undo!"; - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - - if (!model.canUndoAddressBook()) { - throw new CommandException(MESSAGE_FAILURE); - } - - model.undoAddressBook(); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 3b8bfa035e83..000000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; -import java.util.stream.Stream; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new AddCommand object - */ -public class AddCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } - - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java deleted file mode 100644 index b7d57f5db86a..000000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,92 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.HistoryCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.commands.RedoCommand; -import seedu.address.logic.commands.SelectCommand; -import seedu.address.logic.commands.UndoCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses user input. - */ -public class AddressBookParser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - * @throws ParseException if the user input does not conform the expected format - */ - public Command parseCommand(String userInput) throws ParseException { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case SelectCommand.COMMAND_WORD: - return new SelectCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case HistoryCommand.COMMAND_WORD: - return new HistoryCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - case UndoCommand.COMMAND_WORD: - return new UndoCommand(); - - case RedoCommand.COMMAND_WORD: - return new RedoCommand(); - - default: - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java deleted file mode 100644 index 75b1a9bf1190..000000000000 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ /dev/null @@ -1,15 +0,0 @@ -package seedu.address.logic.parser; - -/** - * Contains Command Line Interface (CLI) syntax definitions common to multiple commands - */ -public class CliSyntax { - - /* Prefix definitions */ - public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); - public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); - -} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java deleted file mode 100644 index 845644b7dea1..000000000000 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ /dev/null @@ -1,82 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new EditCommand object - */ -public class EditCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public EditCommand parse(String args) throws ParseException { - requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - Index index; - - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); - } - - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); - } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); - } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); - } - - return new EditCommand(index, editPersonDescriptor); - } - - /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. - */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index b186a967cb94..000000000000 --- 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 an 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 b117acb9c55b..000000000000 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ /dev/null @@ -1,124 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Contains utility methods used for parsing strings in the various *Parser classes. - */ -public class ParserUtil { - - public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; - - /** - * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be - * trimmed. - * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). - */ - public static Index parseIndex(String oneBasedIndex) throws ParseException { - String trimmedIndex = oneBasedIndex.trim(); - if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { - throw new ParseException(MESSAGE_INVALID_INDEX); - } - return Index.fromOneBased(Integer.parseInt(trimmedIndex)); - } - - /** - * Parses a {@code String name} into a {@code Name}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code name} is invalid. - */ - public static Name parseName(String name) throws ParseException { - requireNonNull(name); - String trimmedName = name.trim(); - if (!Name.isValidName(trimmedName)) { - throw new ParseException(Name.MESSAGE_CONSTRAINTS); - } - return new Name(trimmedName); - } - - /** - * Parses a {@code String phone} into a {@code Phone}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code phone} is invalid. - */ - public static Phone parsePhone(String phone) throws ParseException { - requireNonNull(phone); - String trimmedPhone = phone.trim(); - if (!Phone.isValidPhone(trimmedPhone)) { - throw new ParseException(Phone.MESSAGE_CONSTRAINTS); - } - return new Phone(trimmedPhone); - } - - /** - * Parses a {@code String address} into an {@code Address}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code address} is invalid. - */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_CONSTRAINTS); - } - return new Address(trimmedAddress); - } - - /** - * Parses a {@code String email} into an {@code Email}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code email} is invalid. - */ - public static Email parseEmail(String email) throws ParseException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new ParseException(Email.MESSAGE_CONSTRAINTS); - } - return new Email(trimmedEmail); - } - - /** - * Parses a {@code String tag} into a {@code Tag}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code tag} is invalid. - */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(trimmedTag); - } - - /** - * Parses {@code Collection tags} into a {@code Set}. - */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); - } - return tagSet; - } -} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 30557cf81ee7..000000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,144 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import javafx.beans.InvalidationListener; -import javafx.collections.ObservableList; -import seedu.address.commons.util.InvalidationListenerManager; -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; - private final InvalidationListenerManager invalidationListenerManager = new InvalidationListenerManager(); - - /* - * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html - * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. - */ - { - persons = new UniquePersonList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - this.persons.setPersons(persons); - indicateModified(); - } - - /** - * 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); - indicateModified(); - } - - /** - * 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); - indicateModified(); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. - */ - public void removePerson(Person key) { - persons.remove(key); - indicateModified(); - } - - @Override - public void addListener(InvalidationListener listener) { - invalidationListenerManager.addListener(listener); - } - - @Override - public void removeListener(InvalidationListener listener) { - invalidationListenerManager.removeListener(listener); - } - - /** - * Notifies listeners that the address book has been modified. - */ - protected void indicateModified() { - invalidationListenerManager.callListeners(this); - } - - //// 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 e857533821b6..000000000000 --- a/src/main/java/seedu/address/model/Model.java +++ /dev/null @@ -1,130 +0,0 @@ -package seedu.address.model; - -import java.nio.file.Path; -import java.util.function.Predicate; - -import javafx.beans.property.ReadOnlyProperty; -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); - - /** - * Returns true if the model has previous address book states to restore. - */ - boolean canUndoAddressBook(); - - /** - * Returns true if the model has undone address book states to restore. - */ - boolean canRedoAddressBook(); - - /** - * Restores the model's address book to its previous state. - */ - void undoAddressBook(); - - /** - * Restores the model's address book to its previously undone state. - */ - void redoAddressBook(); - - /** - * Saves the current address book state for undo/redo. - */ - void commitAddressBook(); - - /** - * Selected person in the filtered person list. - * null if no person is selected. - */ - ReadOnlyProperty selectedPersonProperty(); - - /** - * Returns the selected person in the filtered person list. - * null if no person is selected. - */ - Person getSelectedPerson(); - - /** - * Sets the selected person in the filtered person list. - */ - void setSelectedPerson(Person person); -} 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 b56806232814..000000000000 --- a/src/main/java/seedu/address/model/ModelManager.java +++ /dev/null @@ -1,235 +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.Objects; -import java.util.function.Predicate; -import java.util.logging.Logger; - -import javafx.beans.property.ReadOnlyProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.collections.ListChangeListener; -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; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * 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 VersionedAddressBook versionedAddressBook; - private final UserPrefs userPrefs; - private final FilteredList filteredPersons; - private final SimpleObjectProperty selectedPerson = new SimpleObjectProperty<>(); - - /** - * 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); - - versionedAddressBook = new VersionedAddressBook(addressBook); - this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(versionedAddressBook.getPersonList()); - filteredPersons.addListener(this::ensureSelectedPersonIsValid); - } - - 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) { - versionedAddressBook.resetData(addressBook); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return versionedAddressBook; - } - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return versionedAddressBook.hasPerson(person); - } - - @Override - public void deletePerson(Person target) { - versionedAddressBook.removePerson(target); - } - - @Override - public void addPerson(Person person) { - versionedAddressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - } - - @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - versionedAddressBook.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); - } - - //=========== Undo/Redo ================================================================================= - - @Override - public boolean canUndoAddressBook() { - return versionedAddressBook.canUndo(); - } - - @Override - public boolean canRedoAddressBook() { - return versionedAddressBook.canRedo(); - } - - @Override - public void undoAddressBook() { - versionedAddressBook.undo(); - } - - @Override - public void redoAddressBook() { - versionedAddressBook.redo(); - } - - @Override - public void commitAddressBook() { - versionedAddressBook.commit(); - } - - //=========== Selected person =========================================================================== - - @Override - public ReadOnlyProperty selectedPersonProperty() { - return selectedPerson; - } - - @Override - public Person getSelectedPerson() { - return selectedPerson.getValue(); - } - - @Override - public void setSelectedPerson(Person person) { - if (person != null && !filteredPersons.contains(person)) { - throw new PersonNotFoundException(); - } - selectedPerson.setValue(person); - } - - /** - * Ensures {@code selectedPerson} is a valid person in {@code filteredPersons}. - */ - private void ensureSelectedPersonIsValid(ListChangeListener.Change change) { - while (change.next()) { - if (selectedPerson.getValue() == null) { - // null is always a valid selected person, so we do not need to check that it is valid anymore. - return; - } - - boolean wasSelectedPersonReplaced = change.wasReplaced() && change.getAddedSize() == change.getRemovedSize() - && change.getRemoved().contains(selectedPerson.getValue()); - if (wasSelectedPersonReplaced) { - // Update selectedPerson to its new value. - int index = change.getRemoved().indexOf(selectedPerson.getValue()); - selectedPerson.setValue(change.getAddedSubList().get(index)); - continue; - } - - boolean wasSelectedPersonRemoved = change.getRemoved().stream() - .anyMatch(removedPerson -> selectedPerson.getValue().isSamePerson(removedPerson)); - if (wasSelectedPersonRemoved) { - // Select the person that came before it in the list, - // or clear the selection if there is no such person. - selectedPerson.setValue(change.getFrom() > 0 ? change.getList().get(change.getFrom() - 1) : null); - } - } - } - - @Override - public boolean equals(Object obj) { - // short circuit if same object - if (obj == this) { - return true; - } - - // instanceof handles nulls - if (!(obj instanceof ModelManager)) { - return false; - } - - // state check - ModelManager other = (ModelManager) obj; - return versionedAddressBook.equals(other.versionedAddressBook) - && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons) - && Objects.equals(selectedPerson.get(), other.selectedPerson.get()); - } - -} 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 6a301434b33b..000000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,18 +0,0 @@ -package seedu.address.model; - -import javafx.beans.Observable; -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook extends Observable { - - /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. - */ - ObservableList getPersonList(); - -} diff --git a/src/main/java/seedu/address/model/VersionedAddressBook.java b/src/main/java/seedu/address/model/VersionedAddressBook.java deleted file mode 100644 index e17a9e3ba4ab..000000000000 --- a/src/main/java/seedu/address/model/VersionedAddressBook.java +++ /dev/null @@ -1,110 +0,0 @@ -package seedu.address.model; - -import java.util.ArrayList; -import java.util.List; - -/** - * {@code AddressBook} that keeps track of its own history. - */ -public class VersionedAddressBook extends AddressBook { - - private final List addressBookStateList; - private int currentStatePointer; - - public VersionedAddressBook(ReadOnlyAddressBook initialState) { - super(initialState); - - addressBookStateList = new ArrayList<>(); - addressBookStateList.add(new AddressBook(initialState)); - currentStatePointer = 0; - } - - /** - * Saves a copy of the current {@code AddressBook} state at the end of the state list. - * Undone states are removed from the state list. - */ - public void commit() { - removeStatesAfterCurrentPointer(); - addressBookStateList.add(new AddressBook(this)); - currentStatePointer++; - indicateModified(); - } - - private void removeStatesAfterCurrentPointer() { - addressBookStateList.subList(currentStatePointer + 1, addressBookStateList.size()).clear(); - } - - /** - * Restores the address book to its previous state. - */ - public void undo() { - if (!canUndo()) { - throw new NoUndoableStateException(); - } - currentStatePointer--; - resetData(addressBookStateList.get(currentStatePointer)); - } - - /** - * Restores the address book to its previously undone state. - */ - public void redo() { - if (!canRedo()) { - throw new NoRedoableStateException(); - } - currentStatePointer++; - resetData(addressBookStateList.get(currentStatePointer)); - } - - /** - * Returns true if {@code undo()} has address book states to undo. - */ - public boolean canUndo() { - return currentStatePointer > 0; - } - - /** - * Returns true if {@code redo()} has address book states to redo. - */ - public boolean canRedo() { - return currentStatePointer < addressBookStateList.size() - 1; - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof VersionedAddressBook)) { - return false; - } - - VersionedAddressBook otherVersionedAddressBook = (VersionedAddressBook) other; - - // state check - return super.equals(otherVersionedAddressBook) - && addressBookStateList.equals(otherVersionedAddressBook.addressBookStateList) - && currentStatePointer == otherVersionedAddressBook.currentStatePointer; - } - - /** - * Thrown when trying to {@code undo()} but can't. - */ - public static class NoUndoableStateException extends RuntimeException { - private NoUndoableStateException() { - super("Current state pointer at start of addressBookState list, unable to undo."); - } - } - - /** - * Thrown when trying to {@code redo()} but can't. - */ - public static class NoRedoableStateException extends RuntimeException { - private NoRedoableStateException() { - super("Current state pointer at end of addressBookState list, unable to redo."); - } - } -} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index 60472ca22a09..000000000000 --- 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 a5bbe0b6a5fc..000000000000 --- a/src/main/java/seedu/address/model/person/Email.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's email in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} - */ -public class Email { - - private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-"; - public static final String MESSAGE_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 79244d71cf73..000000000000 --- 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 c9b5868427ca..000000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate { - private final List keywords; - - public NameContainsKeywordsPredicate(List keywords) { - this.keywords = keywords; - } - - @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 557a7a60cd51..000000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - - /** - * Returns true if both persons of the same name have at least one other identity field that is the same. - * This defines a weaker notion of equality between two persons. - */ - public boolean isSamePerson(Person otherPerson) { - if (otherPerson == this) { - return true; - } - - return otherPerson != null - && otherPerson.getName().equals(getName()) - && (otherPerson.getPhone().equals(getPhone()) || otherPerson.getEmail().equals(getEmail())); - } - - /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof Person)) { - return false; - } - - Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) - && otherPerson.getPhone().equals(getPhone()) - && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append(" Phone: ") - .append(getPhone()) - .append(" Email: ") - .append(getEmail()) - .append(" Address: ") - .append(getAddress()) - .append(" Tags: "); - getTags().forEach(builder::append); - return builder.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java deleted file mode 100644 index 872c76b382fd..000000000000 --- a/src/main/java/seedu/address/model/person/Phone.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's phone number in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} - */ -public class Phone { - - - public static final String MESSAGE_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 0fee4fe57e6b..000000000000 --- 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 d7290f594423..000000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,11 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same - * identity). - */ -public class DuplicatePersonException extends RuntimeException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index fa764426ca73..000000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java deleted file mode 100644 index b0ea7e7dad7f..000000000000 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ /dev/null @@ -1,54 +0,0 @@ -package seedu.address.model.tag; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Tag in the address book. - * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} - */ -public class Tag { - - public static final String MESSAGE_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 1806da4facfa..000000000000 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.model.util; - -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Contains utility methods for populating {@code AddressBook} with sample data. - */ -public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) - }; - } - - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); - } - return sampleAb; - } - - /** - * Returns a tag set containing the list of strings given. - */ - public static Set getTagSet(String... strings) { - return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); - } - -} diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index 4599182b3f92..000000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - Path getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional readAddressBook() throws DataConversionException, IOException; - - /** - * @see #getAddressBookFilePath() - */ - Optional readAddressBook(Path filePath) throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java deleted file mode 100644 index a6321cec2eac..000000000000 --- 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 0df22bdb7546..000000000000 --- 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 dfab9daaa0d3..000000000000 --- 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 5efd834091d4..000000000000 --- 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 beda8bd9f11b..000000000000 --- 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 e4f452b6cbf4..000000000000 --- 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/BrowserPanel.java b/src/main/java/seedu/address/ui/BrowserPanel.java deleted file mode 100644 index 1e76124a59e2..000000000000 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.ui; - -import static java.util.Objects.requireNonNull; - -import java.net.URL; -import java.util.logging.Logger; - -import javafx.application.Platform; -import javafx.beans.value.ObservableValue; -import javafx.event.Event; -import javafx.fxml.FXML; -import javafx.scene.layout.Region; -import javafx.scene.web.WebView; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * The Browser Panel of the App. - */ -public class BrowserPanel extends UiPart { - - public static final URL DEFAULT_PAGE = - requireNonNull(MainApp.class.getResource(FXML_FILE_FOLDER + "default.html")); - public static final String SEARCH_PAGE_URL = "https://se-edu.github.io/dummy-search-page/?name="; - - private static final String FXML = "BrowserPanel.fxml"; - - private final Logger logger = LogsCenter.getLogger(getClass()); - - @FXML - private WebView browser; - - public BrowserPanel(ObservableValue selectedPerson) { - super(FXML); - - // To prevent triggering events for typing inside the loaded Web page. - getRoot().setOnKeyPressed(Event::consume); - - // Load person page when selected person changes. - selectedPerson.addListener((observable, oldValue, newValue) -> { - if (newValue == null) { - loadDefaultPage(); - return; - } - loadPersonPage(newValue); - }); - - loadDefaultPage(); - } - - private void loadPersonPage(Person person) { - loadPage(SEARCH_PAGE_URL + person.getName().fullName); - } - - public void loadPage(String url) { - Platform.runLater(() -> browser.getEngine().load(url)); - } - - /** - * Loads a default HTML file with a background that matches the general theme. - */ - private void loadDefaultPage() { - loadPage(DEFAULT_PAGE.toExternalForm()); - } - -} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java deleted file mode 100644 index ac165736001d..000000000000 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ /dev/null @@ -1,201 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.event.ActionEvent; -import javafx.fxml.FXML; -import javafx.scene.control.MenuItem; -import javafx.scene.control.TextInputControl; -import javafx.scene.input.KeyCombination; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.StackPane; -import javafx.stage.Stage; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.Logic; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * The Main Window. Provides the basic application layout containing - * a menu bar and space where other JavaFX elements can be placed. - */ -public class MainWindow extends UiPart { - - private static final String FXML = "MainWindow.fxml"; - - private final Logger logger = LogsCenter.getLogger(getClass()); - - private Stage primaryStage; - private Logic logic; - - // Independent Ui parts residing in this Ui container - private BrowserPanel browserPanel; - private PersonListPanel personListPanel; - private ResultDisplay resultDisplay; - private HelpWindow helpWindow; - - @FXML - private StackPane browserPlaceholder; - - @FXML - private StackPane commandBoxPlaceholder; - - @FXML - private MenuItem helpMenuItem; - - @FXML - private StackPane personListPanelPlaceholder; - - @FXML - private StackPane resultDisplayPlaceholder; - - @FXML - private StackPane statusbarPlaceholder; - - public MainWindow(Stage primaryStage, Logic logic) { - super(FXML, primaryStage); - - // Set dependencies - this.primaryStage = primaryStage; - this.logic = logic; - - // Configure the UI - setWindowDefaultSize(logic.getGuiSettings()); - - setAccelerators(); - - helpWindow = new HelpWindow(); - } - - public Stage getPrimaryStage() { - return primaryStage; - } - - private void setAccelerators() { - setAccelerator(helpMenuItem, KeyCombination.valueOf("F1")); - } - - /** - * Sets the accelerator of a MenuItem. - * @param keyCombination the KeyCombination value of the accelerator - */ - private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { - menuItem.setAccelerator(keyCombination); - - /* - * TODO: the code below can be removed once the bug reported here - * https://bugs.openjdk.java.net/browse/JDK-8131666 - * is fixed in later version of SDK. - * - * According to the bug report, TextInputControl (TextField, TextArea) will - * consume function-key events. Because CommandBox contains a TextField, and - * ResultDisplay contains a TextArea, thus some accelerators (e.g F1) will - * not work when the focus is in them because the key event is consumed by - * the TextInputControl(s). - * - * For now, we add following event filter to capture such key events and open - * help window purposely so to support accelerators even when focus is - * in CommandBox or ResultDisplay. - */ - getRoot().addEventFilter(KeyEvent.KEY_PRESSED, event -> { - if (event.getTarget() instanceof TextInputControl && keyCombination.match(event)) { - menuItem.getOnAction().handle(new ActionEvent()); - event.consume(); - } - }); - } - - /** - * Fills up all the placeholders of this window. - */ - void fillInnerParts() { - browserPanel = new BrowserPanel(logic.selectedPersonProperty()); - browserPlaceholder.getChildren().add(browserPanel.getRoot()); - - personListPanel = new PersonListPanel(logic.getFilteredPersonList(), logic.selectedPersonProperty(), - logic::setSelectedPerson); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); - - resultDisplay = new ResultDisplay(); - resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath(), logic.getAddressBook()); - statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); - - CommandBox commandBox = new CommandBox(this::executeCommand, logic.getHistory()); - commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); - } - - /** - * Sets the default size based on {@code guiSettings}. - */ - private void setWindowDefaultSize(GuiSettings guiSettings) { - primaryStage.setHeight(guiSettings.getWindowHeight()); - primaryStage.setWidth(guiSettings.getWindowWidth()); - if (guiSettings.getWindowCoordinates() != null) { - primaryStage.setX(guiSettings.getWindowCoordinates().getX()); - primaryStage.setY(guiSettings.getWindowCoordinates().getY()); - } - } - - /** - * Opens the help window or focuses on it if it's already opened. - */ - @FXML - public void handleHelp() { - if (!helpWindow.isShowing()) { - helpWindow.show(); - } else { - helpWindow.focus(); - } - } - - void show() { - primaryStage.show(); - } - - /** - * Closes the application. - */ - @FXML - private void handleExit() { - GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), - (int) primaryStage.getX(), (int) primaryStage.getY()); - logic.setGuiSettings(guiSettings); - helpWindow.hide(); - primaryStage.hide(); - } - - public PersonListPanel getPersonListPanel() { - return personListPanel; - } - - /** - * Executes the command and returns the result. - * - * @see seedu.address.logic.Logic#execute(String) - */ - private CommandResult executeCommand(String commandText) throws CommandException, ParseException { - try { - CommandResult commandResult = logic.execute(commandText); - logger.info("Result: " + commandResult.getFeedbackToUser()); - resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); - - if (commandResult.isShowHelp()) { - handleHelp(); - } - - if (commandResult.isExit()) { - handleExit(); - } - - return commandResult; - } catch (CommandException | ParseException e) { - logger.info("Invalid command: " + commandText); - resultDisplay.setFeedbackToUser(e.getMessage()); - throw e; - } - } -} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index f6727ea83abd..000000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,70 +0,0 @@ -package seedu.address.ui; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import seedu.address.model.person.Person; - -/** - * An UI component that displays information of a {@code Person}. - */ -public class PersonCard extends UiPart { - - private static final String FXML = "PersonListCard.fxml"; - - /** - * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. - * As a consequence, UI elements' variable names cannot be set to such keywords - * or an exception will be thrown by JavaFX during runtime. - * - * @see The issue on AddressBook level 4 - */ - - public final Person person; - - @FXML - private HBox cardPane; - @FXML - private Label name; - @FXML - private Label id; - @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private FlowPane tags; - - public PersonCard(Person person, int displayedIndex) { - super(FXML); - this.person = person; - id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof PersonCard)) { - return false; - } - - // state check - PersonCard card = (PersonCard) other; - return id.getText().equals(card.id.getText()) - && person.equals(card.person); - } -} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index 5ca3fa4fc671..000000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,71 +0,0 @@ -package seedu.address.ui; - -import java.util.Objects; -import java.util.function.Consumer; -import java.util.logging.Logger; - -import javafx.beans.value.ObservableValue; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.layout.Region; -import seedu.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, ObservableValue selectedPerson, - Consumer onSelectedPersonChange) { - super(FXML); - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - personListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { - logger.fine("Selection in person list panel changed to : '" + newValue + "'"); - onSelectedPersonChange.accept(newValue); - }); - selectedPerson.addListener((observable, oldValue, newValue) -> { - logger.fine("Selected person changed to: " + newValue); - - // Don't modify selection if we are already selecting the selected person, - // otherwise we would have an infinite loop. - if (Objects.equals(personListView.getSelectionModel().getSelectedItem(), newValue)) { - return; - } - - if (newValue == null) { - personListView.getSelectionModel().clearSelection(); - } else { - int index = personListView.getItems().indexOf(newValue); - personListView.scrollTo(index); - personListView.getSelectionModel().clearAndSelect(index); - } - }); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java deleted file mode 100644 index b22e1f525256..000000000000 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ /dev/null @@ -1,69 +0,0 @@ -package seedu.address.ui; - -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Clock; -import java.util.Date; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.Region; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * A ui for the status bar that is displayed at the footer of the application. - */ -public class StatusBarFooter extends UiPart { - - public static final String SYNC_STATUS_INITIAL = "Not updated yet in this session"; - public static final String SYNC_STATUS_UPDATED = "Last Updated: %s"; - - /** - * Used to generate time stamps. - * - * TODO: change clock to an instance variable. - * We leave it as a static variable because manual dependency injection - * will require passing down the clock reference all the way from MainApp, - * but it should be easier once we have factories/DI frameworks. - */ - private static Clock clock = Clock.systemDefaultZone(); - - private static final String FXML = "StatusBarFooter.fxml"; - - @FXML - private Label syncStatus; - @FXML - private Label saveLocationStatus; - - - public StatusBarFooter(Path saveLocation, ReadOnlyAddressBook addressBook) { - super(FXML); - addressBook.addListener(observable -> updateSyncStatus()); - syncStatus.setText(SYNC_STATUS_INITIAL); - saveLocationStatus.setText(Paths.get(".").resolve(saveLocation).toString()); - } - - /** - * Sets the clock used to determine the current time. - */ - public static void setClock(Clock clock) { - StatusBarFooter.clock = clock; - } - - /** - * Returns the clock currently in use. - */ - public static Clock getClock() { - return clock; - } - - /** - * Updates "last updated" status to the current time. - */ - private void updateSyncStatus() { - long now = clock.millis(); - String lastUpdated = new Date(now).toString(); - syncStatus.setText(String.format(SYNC_STATUS_UPDATED, lastUpdated)); - } - -} diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/seedu/knowitall/AppParameters.java similarity index 93% rename from src/main/java/seedu/address/AppParameters.java rename to src/main/java/seedu/knowitall/AppParameters.java index ab552c398f3d..7e5fa19024dd 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/seedu/knowitall/AppParameters.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.knowitall; import java.nio.file.Path; import java.nio.file.Paths; @@ -7,8 +7,8 @@ import java.util.logging.Logger; import javafx.application.Application; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.FileUtil; +import seedu.knowitall.commons.core.LogsCenter; +import seedu.knowitall.commons.util.FileUtil; /** * Represents the parsed command-line parameters given to the application. diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/knowitall/MainApp.java similarity index 51% rename from src/main/java/seedu/address/MainApp.java rename to src/main/java/seedu/knowitall/MainApp.java index a92d4d5d71f0..5c29087c2bf5 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/knowitall/MainApp.java @@ -1,35 +1,38 @@ -package seedu.address; +package seedu.knowitall; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.logging.Logger; +import java.util.stream.Stream; import javafx.application.Application; import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.core.Version; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.ConfigUtil; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; -import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; -import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; -import seedu.address.storage.JsonAddressBookStorage; -import seedu.address.storage.JsonUserPrefsStorage; -import seedu.address.storage.Storage; -import seedu.address.storage.StorageManager; -import seedu.address.storage.UserPrefsStorage; -import seedu.address.ui.Ui; -import seedu.address.ui.UiManager; +import seedu.knowitall.commons.core.Config; +import seedu.knowitall.commons.core.LogsCenter; +import seedu.knowitall.commons.core.Version; +import seedu.knowitall.commons.exceptions.DataConversionException; +import seedu.knowitall.commons.util.ConfigUtil; +import seedu.knowitall.commons.util.StringUtil; +import seedu.knowitall.logic.Logic; +import seedu.knowitall.logic.LogicManager; +import seedu.knowitall.model.Model; +import seedu.knowitall.model.ModelManager; +import seedu.knowitall.model.ReadOnlyCardFolder; +import seedu.knowitall.model.ReadOnlyUserPrefs; +import seedu.knowitall.model.UserPrefs; +import seedu.knowitall.model.util.SampleDataUtil; +import seedu.knowitall.storage.CardFolderStorage; +import seedu.knowitall.storage.JsonCardFolderStorage; +import seedu.knowitall.storage.JsonUserPrefsStorage; +import seedu.knowitall.storage.Storage; +import seedu.knowitall.storage.StorageManager; +import seedu.knowitall.storage.UserPrefsStorage; +import seedu.knowitall.ui.Ui; +import seedu.knowitall.ui.UiManager; /** * The main entry point to the application. @@ -48,7 +51,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing CardFolder ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -56,12 +59,35 @@ 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); + List cardFolderStorageList = new ArrayList<>(); + + Path cardFolderFilesPath = userPrefs.getcardFolderFilesPath(); + + boolean withSample = false; + if (Files.isDirectory(cardFolderFilesPath)) { + Stream stream = Files.walk(cardFolderFilesPath); + if (stream != null) { + stream.filter(Files::isRegularFile) + .filter(JsonCardFolderStorage::isCardFolderStorage) + .forEach(file -> cardFolderStorageList.add(new JsonCardFolderStorage(file))); + } + } + if (cardFolderStorageList.isEmpty()) { + logger.info("Folders not found. Will be starting with a sample CardFolder"); + Path samplePath = cardFolderFilesPath.resolve(SampleDataUtil.getSampleFolderFileName()); + cardFolderStorageList.add(new JsonCardFolderStorage(samplePath)); + withSample = true; + } + + storage = new StorageManager(cardFolderStorageList, userPrefsStorage); initLogging(config); - model = initModelManager(storage, userPrefs); + if (withSample) { + model = initModelManagerWithSample(userPrefs); + } else { + model = initModelManager(storage, userPrefs); + } logic = new LogicManager(model, storage); @@ -69,31 +95,47 @@ public void init() throws Exception { } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found, - * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. + * Returns a {@code ModelManager} with the data from {@code storage}'s card folder and {@code userPrefs}.
+ * All folders in valid formats that are found will be read. If none are found, the data from the sample card folder + * will be used instead. */ - private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { + List initialCardFolders; + initialCardFolders = new ArrayList<>(); + + // read all valid card folders try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + storage.readCardFolders(initialCardFolders); + } catch (Exception e) { + if (e instanceof DataConversionException) { + logger.warning("Data file not in the correct format."); + } else if (e instanceof IOException) { + logger.warning("Problem while reading from the file."); + } else { + logger.warning("Unknown error while reading from file."); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); - } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); - } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); } - return new ModelManager(initialData, userPrefs); + // if no card folder is valid, then start with a sample one. + if (initialCardFolders.isEmpty()) { + logger.warning("No CardFolders read. Will be starting with a sample CardFolder"); + return initModelManagerWithSample(userPrefs); + } + + return new ModelManager(initialCardFolders, userPrefs); + } + + /** + * Returns a {@code ModelManager} with data from the sample card folder. + */ + Model initModelManagerWithSample(ReadOnlyUserPrefs userPrefs) { + List sampleCardFolders = new ArrayList<>(); + sampleCardFolders.add(SampleDataUtil.getSampleCardFolder()); + + return new ModelManager(sampleCardFolders, userPrefs); } - private void initLogging(Config config) { + void initLogging(Config config) { LogsCenter.init(config); } @@ -151,7 +193,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 CardFolder"); initializedPrefs = new UserPrefs(); } @@ -167,13 +209,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting CardFolder " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping card folder ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/knowitall/commons/core/Config.java similarity index 97% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/seedu/knowitall/commons/core/Config.java index 911457455217..45e20a6472d6 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/knowitall/commons/core/Config.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.knowitall.commons.core; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/knowitall/commons/core/GuiSettings.java similarity index 98% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/seedu/knowitall/commons/core/GuiSettings.java index 5ace559ad156..dad24fc43bc6 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/knowitall/commons/core/GuiSettings.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.knowitall.commons.core; import java.awt.Point; import java.io.Serializable; diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/knowitall/commons/core/LogsCenter.java similarity index 96% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/seedu/knowitall/commons/core/LogsCenter.java index 431e7185e762..2731003ed700 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/knowitall/commons/core/LogsCenter.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.knowitall.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 = "cardfolder.log"; private static Level currentLogLevel = Level.INFO; private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; @@ -27,7 +27,7 @@ public class LogsCenter { /** * Initializes with a custom log level (specified in the {@code config} object) * Loggers obtained *AFTER* this initialization will have their logging level changed
- * Logging levels for existing loggers will only be updated if the logger with the same name + * Logging levels for existing loggers will only be updated if the logger with the same question * is requested again from the LogsCenter. */ public static void init(Config config) { diff --git a/src/main/java/seedu/knowitall/commons/core/Messages.java b/src/main/java/seedu/knowitall/commons/core/Messages.java new file mode 100755 index 000000000000..62c2f36068f6 --- /dev/null +++ b/src/main/java/seedu/knowitall/commons/core/Messages.java @@ -0,0 +1,35 @@ +package seedu.knowitall.commons.core; + +/** + * Container for user visible messages. + */ +public class Messages { + + public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; + public static final String MESSAGE_MAX_COMMAND_LENGTH_EXCEEDED = "Maximum command length exceeded, commands should" + + " be less than %d characters"; + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; + public static final String MESSAGE_INVALID_CARD_DISPLAYED_INDEX = "The card index provided is invalid"; + public static final String MESSAGE_INVALID_FOLDER_DISPLAYED_INDEX = "The card folder index provided is invalid"; + public static final String MESSAGE_CARDS_LISTED_OVERVIEW = "%1$d cards listed!"; + public static final String MESSAGE_INVALID_COMMAND_OUTSIDE_FULLSCREEN = "This command is not valid outside a test" + + " or report"; + public static final String MESSAGE_INVALID_ANSWER_COMMAND = "Answer command is valid only when a question is " + + "displayed"; + public static final String MESSAGE_INVALID_REVEAL_COMMAND = "Reveal command is valid only when a question is " + + "displayed"; + public static final String MESSAGE_NO_NEGATIVE_INDEX = "Negative index not allowed!"; + public static final String MESSAGE_INVALID_COMMAND_ON_EMPTY_FOLDER = "This command is not valid on an empty" + + " folder"; + public static final String MESSAGE_INVALID_NEXT_COMMAND = "Next command is valid only when this question has been" + + " answered"; + public static final String MESSAGE_INVALID_COMMAND_OUTSIDE_FOLDER = "Command can only be executed in folder"; + public static final String MESSAGE_INVALID_COMMAND_INSIDE_FOLDER = "Command can only be executed in home directory"; + public static final String MESSAGE_INVALID_NUMBER_OF_CARD_ARGUMENTS = "Cards can only accept a maximum of %d %s"; + public static final String MESSAGE_ILLEGAL_OPTION_CANNOT_BE_SAME_AS_ANSWER = "Incorrect MCQ options cannot be same" + + " as the correct answer"; + public static final String MESSAGE_CSV_MANAGER_NOT_INITIALIZED = "Unable to carry out import and export commands"; + public static final String MESSAGE_INCORRECT_CSV_FILE_HEADER = "Incorrect Csv file headers. Check that the\n" + + "csv file contains question,answer,hints,option,option,option header"; + public static final String MESSAGE_EMPTY_CSV_FILE = "Empty csv file!"; +} diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/knowitall/commons/core/Version.java similarity index 98% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/seedu/knowitall/commons/core/Version.java index e117f91b3b2e..e0c8bd21aaf6 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/seedu/knowitall/commons/core/Version.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.knowitall.commons.core; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/knowitall/commons/core/index/Index.java similarity index 92% rename from src/main/java/seedu/address/commons/core/index/Index.java rename to src/main/java/seedu/knowitall/commons/core/index/Index.java index 19536439c099..383be3518aad 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/knowitall/commons/core/index/Index.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core.index; +package seedu.knowitall.commons.core.index; /** * Represents a zero-based or one-based index. @@ -51,4 +51,9 @@ public boolean equals(Object other) { || (other instanceof Index // instanceof handles nulls && zeroBasedIndex == ((Index) other).zeroBasedIndex); // state check } + + + public String displayIndex() { + return Integer.toString(getOneBased()); + } } diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/seedu/knowitall/commons/exceptions/DataConversionException.java similarity index 83% rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java rename to src/main/java/seedu/knowitall/commons/exceptions/DataConversionException.java index 1f689bd8e3f9..648e9af06806 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java +++ b/src/main/java/seedu/knowitall/commons/exceptions/DataConversionException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.knowitall.commons.exceptions; /** * Represents an error during conversion of data from one format to another diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/seedu/knowitall/commons/exceptions/IllegalValueException.java similarity index 92% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/seedu/knowitall/commons/exceptions/IllegalValueException.java index 19124db485c9..bbe8dfb38f57 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/seedu/knowitall/commons/exceptions/IllegalValueException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.knowitall.commons.exceptions; /** * Signals that some given data does not fulfill some constraints. diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/seedu/knowitall/commons/util/AppUtil.java similarity index 93% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/seedu/knowitall/commons/util/AppUtil.java index da90201dfd64..9f808ea12ef1 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/seedu/knowitall/commons/util/AppUtil.java @@ -1,9 +1,9 @@ -package seedu.address.commons.util; +package seedu.knowitall.commons.util; import static java.util.Objects.requireNonNull; import javafx.scene.image.Image; -import seedu.address.MainApp; +import seedu.knowitall.MainApp; /** * A container for App specific utility functions diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/seedu/knowitall/commons/util/CollectionUtil.java similarity index 96% rename from src/main/java/seedu/address/commons/util/CollectionUtil.java rename to src/main/java/seedu/knowitall/commons/util/CollectionUtil.java index eafe4dfd6818..c9720039bb38 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/seedu/knowitall/commons/util/CollectionUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.knowitall.commons.util; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/seedu/knowitall/commons/util/ConfigUtil.java similarity index 76% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/seedu/knowitall/commons/util/ConfigUtil.java index f7f8a2bd44c0..0342d39d96c4 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/seedu/knowitall/commons/util/ConfigUtil.java @@ -1,11 +1,11 @@ -package seedu.address.commons.util; +package seedu.knowitall.commons.util; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.core.Config; -import seedu.address.commons.exceptions.DataConversionException; +import seedu.knowitall.commons.core.Config; +import seedu.knowitall.commons.exceptions.DataConversionException; /** * A class for accessing the Config File. diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/knowitall/commons/util/FileUtil.java similarity index 92% rename from src/main/java/seedu/address/commons/util/FileUtil.java rename to src/main/java/seedu/knowitall/commons/util/FileUtil.java index b1e2767cdd92..5bc6d3d6460e 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/knowitall/commons/util/FileUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.knowitall.commons.util; import java.io.IOException; import java.nio.file.Files; @@ -80,4 +80,11 @@ public static void writeToFile(Path file, String content) throws IOException { Files.write(file, content.getBytes(CHARSET)); } + /** + * Deletes given file. + */ + public static void deleteFile(Path file) throws IOException { + Files.delete(file); + } + } diff --git a/src/main/java/seedu/address/commons/util/InvalidationListenerManager.java b/src/main/java/seedu/knowitall/commons/util/InvalidationListenerManager.java similarity index 97% rename from src/main/java/seedu/address/commons/util/InvalidationListenerManager.java rename to src/main/java/seedu/knowitall/commons/util/InvalidationListenerManager.java index 70165336db6d..88700eee31f1 100644 --- a/src/main/java/seedu/address/commons/util/InvalidationListenerManager.java +++ b/src/main/java/seedu/knowitall/commons/util/InvalidationListenerManager.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.knowitall.commons.util; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/knowitall/commons/util/JsonUtil.java similarity index 97% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/seedu/knowitall/commons/util/JsonUtil.java index 8ef609f055df..e34e30d0b7b4 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/knowitall/commons/util/JsonUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.knowitall.commons.util; import static java.util.Objects.requireNonNull; @@ -20,8 +20,8 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; +import seedu.knowitall.commons.core.LogsCenter; +import seedu.knowitall.commons.exceptions.DataConversionException; /** * Converts a Java object instance to JSON and vice versa diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/knowitall/commons/util/StringUtil.java similarity index 95% rename from src/main/java/seedu/address/commons/util/StringUtil.java rename to src/main/java/seedu/knowitall/commons/util/StringUtil.java index 61cc8c9a1cb8..4b9218441d92 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/knowitall/commons/util/StringUtil.java @@ -1,7 +1,7 @@ -package seedu.address.commons.util; +package seedu.knowitall.commons.util; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.knowitall.commons.util.AppUtil.checkArgument; import java.io.PrintWriter; import java.io.StringWriter; diff --git a/src/main/java/seedu/address/logic/CommandHistory.java b/src/main/java/seedu/knowitall/logic/CommandHistory.java similarity index 98% rename from src/main/java/seedu/address/logic/CommandHistory.java rename to src/main/java/seedu/knowitall/logic/CommandHistory.java index 404675e43811..1a3666de299a 100644 --- a/src/main/java/seedu/address/logic/CommandHistory.java +++ b/src/main/java/seedu/knowitall/logic/CommandHistory.java @@ -1,4 +1,4 @@ -package seedu.address.logic; +package seedu.knowitall.logic; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/knowitall/logic/Logic.java b/src/main/java/seedu/knowitall/logic/Logic.java new file mode 100644 index 000000000000..b0f79f4fac84 --- /dev/null +++ b/src/main/java/seedu/knowitall/logic/Logic.java @@ -0,0 +1,76 @@ +package seedu.knowitall.logic; + +import java.nio.file.Path; + +import javafx.beans.property.ReadOnlyProperty; +import javafx.collections.ObservableList; +import seedu.knowitall.commons.core.GuiSettings; +import seedu.knowitall.logic.commands.CommandResult; +import seedu.knowitall.logic.commands.exceptions.CommandException; +import seedu.knowitall.logic.parser.exceptions.ParseException; +import seedu.knowitall.model.ReadOnlyCardFolder; +import seedu.knowitall.model.VersionedCardFolder; +import seedu.knowitall.model.card.Card; + +/** + * 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 CardFolder. + * + * @see seedu.knowitall.model.Model#getActiveCardFolder() + */ + ReadOnlyCardFolder getCardFolder(); + + /** Returns an unmodifiable view of the filtered list of cards */ + ObservableList getActiveFilteredCards(); + + /** Returns an unmodifiable view of the filtered folders list */ + ObservableList getFilteredCardFolders(); + + /** + * Returns an unmodifiable view of the list of commands entered by the user. + * The list is ordered from the least recent command to the most recent command. + */ + ObservableList getHistory(); + + /** + * Returns the user prefs' card folder file path. + */ + Path getcardFolderFilesPath(); + + /** + * Returns the user prefs' GUI settings. + */ + GuiSettings getGuiSettings(); + + /** + * Set the user prefs' GUI settings. + */ + void setGuiSettings(GuiSettings guiSettings); + + /** + * Selected card in the filtered card list. + * null if no card is selected. + * + * @see seedu.knowitall.model.Model#selectedCardProperty() + */ + ReadOnlyProperty selectedCardProperty(); + + /** + * Sets the selected card in the filtered card list. + * + * @see seedu.knowitall.model.Model#setSelectedCard(Card) + */ + void setSelectedCard(Card card); +} diff --git a/src/main/java/seedu/knowitall/logic/LogicManager.java b/src/main/java/seedu/knowitall/logic/LogicManager.java new file mode 100755 index 000000000000..68228586872f --- /dev/null +++ b/src/main/java/seedu/knowitall/logic/LogicManager.java @@ -0,0 +1,145 @@ +package seedu.knowitall.logic; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.logging.Logger; + +import javafx.beans.property.ReadOnlyProperty; +import javafx.collections.ObservableList; +import seedu.knowitall.commons.core.GuiSettings; +import seedu.knowitall.commons.core.LogsCenter; +import seedu.knowitall.logic.commands.Command; +import seedu.knowitall.logic.commands.CommandResult; +import seedu.knowitall.logic.commands.exceptions.CommandException; +import seedu.knowitall.logic.parser.CommandParser; +import seedu.knowitall.logic.parser.exceptions.ParseException; +import seedu.knowitall.model.Model; +import seedu.knowitall.model.ReadOnlyCardFolder; +import seedu.knowitall.model.VersionedCardFolder; +import seedu.knowitall.model.card.Card; +import seedu.knowitall.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 CommandHistory history; + private final CommandParser commandParser; + private boolean cardFolderModified; + private boolean modelModified; + + public LogicManager(Model model, Storage storage) { + this.model = model; + this.storage = storage; + history = new CommandHistory(); + commandParser = new CommandParser(); + + // Add listeners to all card folders and the model + addCardFolderListeners(model); + + // Set modelModified whenever the models' card folders are modified + model.addListener(observable -> modelModified = true); + } + + @Override + public CommandResult execute(String commandText) throws CommandException, ParseException { + logger.info("----------------[USER COMMAND][" + commandText + "]"); + cardFolderModified = false; + modelModified = false; + + CommandResult commandResult; + try { + Command command = commandParser.parseCommand(commandText); + commandResult = command.execute(model, history); + } finally { + history.add(commandText); + } + + if (cardFolderModified) { + logger.info("card folder modified, saving to file."); + try { + storage.saveCardFolder(model.getActiveCardFolder(), model.getActiveCardFolderIndex()); + } catch (IOException ioe) { + throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); + } + } + + if (modelModified) { + logger.info("model is modified, saving to file"); + try { + List cardFolders = model.getCardFolders(); + Path path = model.getcardFolderFilesPath(); + + storage.saveCardFolders(cardFolders, path); + } catch (IOException ioe) { + throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe); + } + + // re-register listeners to all card folders + addCardFolderListeners(model); + } + + return commandResult; + } + + @Override + public ReadOnlyCardFolder getCardFolder() { + return model.getActiveCardFolder(); + } + + @Override + public ObservableList getActiveFilteredCards() { + return model.getActiveFilteredCards(); + } + + @Override + public ObservableList getFilteredCardFolders() { + return model.getFilteredFolders(); + } + + @Override + public ObservableList getHistory() { + return history.getHistory(); + } + + @Override + public Path getcardFolderFilesPath() { + return model.getcardFolderFilesPath(); + } + + @Override + public GuiSettings getGuiSettings() { + return model.getGuiSettings(); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + model.setGuiSettings(guiSettings); + } + + @Override + public ReadOnlyProperty selectedCardProperty() { + return model.selectedCardProperty(); + } + + @Override + public void setSelectedCard(Card card) { + model.setSelectedCard(card); + } + + /** + * Adds listeners to all {@code CardFolders} in {@code model} + */ + private void addCardFolderListeners(Model model) { + // Set cardFolderModified to true whenever the models' card folder is modified. + for (ReadOnlyCardFolder cardFolder : model.getCardFolders()) { + cardFolder.addListener(observable -> cardFolderModified = true); + } + } +} diff --git a/src/main/java/seedu/knowitall/logic/commands/AddCommand.java b/src/main/java/seedu/knowitall/logic/commands/AddCommand.java new file mode 100755 index 000000000000..616773bf758f --- /dev/null +++ b/src/main/java/seedu/knowitall/logic/commands/AddCommand.java @@ -0,0 +1,70 @@ +package seedu.knowitall.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.knowitall.logic.parser.CliSyntax.PREFIX_ANSWER; +import static seedu.knowitall.logic.parser.CliSyntax.PREFIX_HINT; +import static seedu.knowitall.logic.parser.CliSyntax.PREFIX_OPTION; +import static seedu.knowitall.logic.parser.CliSyntax.PREFIX_QUESTION; + +import seedu.knowitall.commons.core.Messages; +import seedu.knowitall.logic.CommandHistory; +import seedu.knowitall.logic.commands.exceptions.CommandException; +import seedu.knowitall.model.Model; +import seedu.knowitall.model.Model.State; +import seedu.knowitall.model.card.Card; + +/** + * Adds a card to the card folder. + */ +public class AddCommand extends Command { + + public static final String COMMAND_WORD = "add"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a card to the card folder. " + + "Parameters: " + + PREFIX_QUESTION + "QUESTION " + + PREFIX_ANSWER + "ANSWER " + + "[" + PREFIX_HINT + "HINT]\n" + + "[" + PREFIX_OPTION + "INCORRECT_OPTION]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_QUESTION + "What is the powerhouse of the cell? " + + PREFIX_ANSWER + "Mitochondria " + + PREFIX_HINT + "Rhymes with Hypochondria "; + + public static final String MESSAGE_SUCCESS = "New card added: %1$s"; + public static final String MESSAGE_DUPLICATE_CARD = "This card already exists in the card folder"; + + private final Card toAdd; + + /** + * Creates an AddCommand to add the specified {@code Card} + */ + public AddCommand(Card card) { + requireNonNull(card); + toAdd = card; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (model.getState() != State.IN_FOLDER) { + throw new CommandException(Messages.MESSAGE_INVALID_COMMAND_OUTSIDE_FOLDER); + } + + if (model.hasCard(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_CARD); + } + + model.addCard(toAdd); + model.commitActiveCardFolder(); + 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/knowitall/logic/commands/AddFolderCommand.java b/src/main/java/seedu/knowitall/logic/commands/AddFolderCommand.java new file mode 100755 index 000000000000..b31e05e54a9b --- /dev/null +++ b/src/main/java/seedu/knowitall/logic/commands/AddFolderCommand.java @@ -0,0 +1,58 @@ +package seedu.knowitall.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.knowitall.commons.core.Messages; +import seedu.knowitall.logic.CommandHistory; +import seedu.knowitall.logic.commands.exceptions.CommandException; +import seedu.knowitall.model.CardFolder; +import seedu.knowitall.model.Model; +import seedu.knowitall.model.Model.State; + +/** + * Adds a card folder. + */ +public class AddFolderCommand extends Command { + + public static final String COMMAND_WORD = "addfolder"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a card folder. " + + "Parameters: " + + "FOLDER_NAME \n" + + "Example: " + COMMAND_WORD + " " + + "Nervous System "; + + public static final String MESSAGE_SUCCESS = "New card folder added: %1$s"; + public static final String MESSAGE_DUPLICATE_CARD_FOLDER = "This card folder already exists"; + + private final String folderNameToAdd; + + /** + * Creates an AddCommand to add the specified {@code Card} + */ + public AddFolderCommand(String folderName) { + requireNonNull(folderName); + folderNameToAdd = folderName; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + if (model.getState() != State.IN_HOMEDIR) { + throw new CommandException(Messages.MESSAGE_INVALID_COMMAND_INSIDE_FOLDER); + } + if (model.hasFolder(folderNameToAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_CARD_FOLDER); + } + + model.addFolder(new CardFolder(folderNameToAdd)); + return new CommandResult(String.format(MESSAGE_SUCCESS, folderNameToAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddFolderCommand // instanceof handles nulls + && folderNameToAdd.equals(((AddFolderCommand) other).folderNameToAdd)); + } +} diff --git a/src/main/java/seedu/knowitall/logic/commands/AnswerCommand.java b/src/main/java/seedu/knowitall/logic/commands/AnswerCommand.java new file mode 100755 index 000000000000..8ffe829fdc7e --- /dev/null +++ b/src/main/java/seedu/knowitall/logic/commands/AnswerCommand.java @@ -0,0 +1,88 @@ +package seedu.knowitall.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.knowitall.model.Model.PREDICATE_SHOW_ALL_CARDS; + +import seedu.knowitall.commons.core.Messages; +import seedu.knowitall.logic.CommandHistory; +import seedu.knowitall.logic.commands.exceptions.CommandException; +import seedu.knowitall.model.Model; +import seedu.knowitall.model.Model.State; +import seedu.knowitall.model.card.Answer; +import seedu.knowitall.model.card.Card; + +/** + * Allows user to input an answer for the currently displayed card, compares it with the + * correct answer in that card and tell the user if it is correct or wrong. + * Answer matching is case insensitive. + */ +public class AnswerCommand extends Command { + + public static final String COMMAND_WORD = "ans"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": User inputs an answer for the" + + " currently displayed card.\n" + + "Parameters: ANSWER \n" + + "Example: " + COMMAND_WORD + " Mitochondrion"; + + public static final String MESSAGE_ANSWER_SUCCESS = "Answer sent successfully"; + public static final String MESSAGE_OPTION_NUMBER_EXPECTED = "MCQ question expects option number as answer"; + + private final Answer attemptedAnswer; + + public AnswerCommand(Answer attemptedAnswer) { + this.attemptedAnswer = attemptedAnswer; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (model.getState() != State.IN_TEST) { + throw new CommandException(Messages.MESSAGE_INVALID_COMMAND_OUTSIDE_FULLSCREEN); + } + if (model.isCardAlreadyAnswered()) { + throw new CommandException(Messages.MESSAGE_INVALID_ANSWER_COMMAND); + } + Card cardToMark = model.getCurrentTestedCard(); + + boolean isAttemptCorrect; + switch (cardToMark.getCardType()) { + case MCQ: + try { + int answerIndex = Integer.parseInt(attemptedAnswer.fullAnswer); + if (answerIndex < 1 || answerIndex > cardToMark.getOptions().size() + 1) { + throw new CommandException(MESSAGE_OPTION_NUMBER_EXPECTED); + } + isAttemptCorrect = model.markAttemptedMcqAnswer(answerIndex); + } catch (NumberFormatException e) { + throw new CommandException(MESSAGE_OPTION_NUMBER_EXPECTED); + } + break; + case SINGLE_ANSWER: + isAttemptCorrect = model.markAttemptedAnswer(attemptedAnswer); + break; + default: + isAttemptCorrect = false; + } + model.setCardAsAnswered(); + + Card scoredCard = model.createScoredCard(cardToMark, isAttemptCorrect); + model.setCard(cardToMark, scoredCard); + model.updateFilteredCard(PREDICATE_SHOW_ALL_CARDS); + model.commitActiveCardFolder(); + + if (isAttemptCorrect) { + return new CommandResult(MESSAGE_ANSWER_SUCCESS, CommandResult.Type.ANSWER_CORRECT); + } else { + return new CommandResult(MESSAGE_ANSWER_SUCCESS, CommandResult.Type.ANSWER_WRONG); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AnswerCommand // instanceof handles nulls + && attemptedAnswer.equals(((AnswerCommand) other).attemptedAnswer)); // state check + } +} diff --git a/src/main/java/seedu/knowitall/logic/commands/ChangeDirectoryCommand.java b/src/main/java/seedu/knowitall/logic/commands/ChangeDirectoryCommand.java new file mode 100644 index 000000000000..628d48cb32d1 --- /dev/null +++ b/src/main/java/seedu/knowitall/logic/commands/ChangeDirectoryCommand.java @@ -0,0 +1,107 @@ +package seedu.knowitall.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.knowitall.commons.core.Messages; +import seedu.knowitall.commons.core.index.Index; +import seedu.knowitall.logic.CommandHistory; +import seedu.knowitall.logic.commands.exceptions.CommandException; +import seedu.knowitall.model.Model; +import seedu.knowitall.model.Model.State; +import seedu.knowitall.model.ReadOnlyCardFolder; + +/** + * Selects a folder identified using it's displayed index in the home directory. Also used to navigate from + * within a folder back to the home directory. + */ +public class ChangeDirectoryCommand extends Command { + + public static final String COMMAND_WORD = "cd"; + + public static final String HOME_SYMBOL = ".."; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Navigates in and out of folders.\n" + + "Parameters (at home directory): INDEX (must be a positive integer)\n" + + "Parameters (inside folder): ..\n" + + "Example (at home directory): " + COMMAND_WORD + " 1\n" + + "Example (inside directory): " + COMMAND_WORD + " " + HOME_SYMBOL; + + public static final String MESSAGE_EXIT_FOLDER_SUCCESS = "Returned to home"; + public static final String MESSAGE_ENTER_FOLDER_SUCCESS = "Entered Card Folder: %1$s"; + + private Index targetIndex; + private final boolean isExitingFolder; + + public ChangeDirectoryCommand(Index targetIndex) { + isExitingFolder = false; + this.targetIndex = targetIndex; + } + + public ChangeDirectoryCommand() { + isExitingFolder = true; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (isExitingFolder) { + return executeExitFolder(model); + } else { + return executeEnterFolder(model); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ChangeDirectoryCommand // instanceof handles nulls + && isExitingFolder == ((ChangeDirectoryCommand) other).isExitingFolder + && sameTargetIndex((ChangeDirectoryCommand) other)); + } + + /** + * Executes the logic to enter a folder. Model cannot already be in a folder. + */ + private CommandResult executeEnterFolder(Model model) throws CommandException { + List cardFolderList = model.getCardFolders(); + + if (model.getState() != State.IN_HOMEDIR) { + throw new CommandException(Messages.MESSAGE_INVALID_COMMAND_INSIDE_FOLDER); + } + + if (targetIndex.getZeroBased() >= cardFolderList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_FOLDER_DISPLAYED_INDEX); + } + model.enterFolder(targetIndex.getZeroBased()); + return new CommandResult(String.format(MESSAGE_ENTER_FOLDER_SUCCESS, targetIndex.getOneBased()), + CommandResult.Type.ENTERED_FOLDER); + } + + /** + * Executes the logic to exit a folder. Model cannot already be outside folders. + */ + private CommandResult executeExitFolder(Model model) throws CommandException { + if (model.getState() != State.IN_FOLDER) { + throw new CommandException(Messages.MESSAGE_INVALID_COMMAND_OUTSIDE_FOLDER); + } + model.exitFolderToHome(); + return new CommandResult(MESSAGE_EXIT_FOLDER_SUCCESS, CommandResult.Type.EXITED_FOLDER); + } + + /** + * Compares the {@code targetIndex} of two ChangeDirectoryCommand objects, returning true if they are equal. + */ + private boolean sameTargetIndex(ChangeDirectoryCommand other) { + // if target indices exist, they must be the same + if (targetIndex != null && other.targetIndex != null) { + return targetIndex.equals(other.targetIndex); + } + + // return true if both indices do not exist, else return false if only one target index do not exist + return (targetIndex == null && other.targetIndex == null); + } +} diff --git a/src/main/java/seedu/knowitall/logic/commands/ClearCommand.java b/src/main/java/seedu/knowitall/logic/commands/ClearCommand.java new file mode 100755 index 000000000000..46f6fe3ec787 --- /dev/null +++ b/src/main/java/seedu/knowitall/logic/commands/ClearCommand.java @@ -0,0 +1,32 @@ +package seedu.knowitall.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.knowitall.commons.core.Messages; +import seedu.knowitall.logic.CommandHistory; +import seedu.knowitall.logic.commands.exceptions.CommandException; +import seedu.knowitall.model.CardFolder; +import seedu.knowitall.model.Model; +import seedu.knowitall.model.Model.State; + +/** + * Clears the card folder. + */ +public class ClearCommand extends Command { + + public static final String COMMAND_WORD = "clear"; + public static final String MESSAGE_SUCCESS = "card folder has been cleared!"; + + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + // Name of CardFolder is preserved in clear operation + if (model.getState() != State.IN_FOLDER) { + throw new CommandException(Messages.MESSAGE_INVALID_COMMAND_OUTSIDE_FOLDER); + } + model.resetCardFolder(new CardFolder(model.getActiveCardFolder().getFolderName())); + model.commitActiveCardFolder(); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/knowitall/logic/commands/Command.java similarity index 76% rename from src/main/java/seedu/address/logic/commands/Command.java rename to src/main/java/seedu/knowitall/logic/commands/Command.java index 34e99d786ec6..9ee859e59fb0 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/knowitall/logic/commands/Command.java @@ -1,8 +1,8 @@ -package seedu.address.logic.commands; +package seedu.knowitall.logic.commands; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import seedu.knowitall.logic.CommandHistory; +import seedu.knowitall.logic.commands.exceptions.CommandException; +import seedu.knowitall.model.Model; /** * Represents a command with hidden internal logic and the ability to be executed. diff --git a/src/main/java/seedu/knowitall/logic/commands/CommandResult.java b/src/main/java/seedu/knowitall/logic/commands/CommandResult.java new file mode 100755 index 000000000000..9daa9cb9c70c --- /dev/null +++ b/src/main/java/seedu/knowitall/logic/commands/CommandResult.java @@ -0,0 +1,89 @@ +package seedu.knowitall.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.Objects; + +import seedu.knowitall.model.card.Card; + +/** + * Represents the result of a command execution. + */ +public class CommandResult { + + private final String feedbackToUser; + + /** + * {@code Type } representing the type of CommandResult and what response should be displayed. + */ + public enum Type { + SHOW_HELP, // Help information should be shown to the user. + IS_EXIT, // The application should exit. + ENTERED_FOLDER, // The side panel should be updated as folder was entered. + EXITED_FOLDER, // The side panel should be updated as folder was exited. + EDITED_FOLDER, // The side panel should be updated as a folder was edited. + START_TEST_SESSION, // The application should enter a test session. + END_TEST_SESSION, // The current test session should end. + ANSWER_CORRECT, + ANSWER_WRONG, + ANSWER_REVEAL, + ENTERED_REPORT, + EXITED_REPORT, + SHOW_NEXT_CARD, // The next card will be displayed in the current test session. + NONE // use for "nothing to do" + } + + private Type type; + + private Card testSessionCard; + + /** + * Constructs a {@code CommandResult} with the specified fields. + */ + public CommandResult(String feedbackToUser, Type type) { + this.feedbackToUser = requireNonNull(feedbackToUser); + this.type = type; + } + + public CommandResult(String feedbackToUser) { + this(feedbackToUser, Type.NONE); + } + + public String getFeedbackToUser() { + return feedbackToUser; + } + + public void setTestSessionCard(Card card) { + testSessionCard = card; + } + + public Card getTestSessionCard() { + return testSessionCard; + } + + public Type getType() { + return this.type; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof CommandResult)) { + return false; + } + + CommandResult otherCommandResult = (CommandResult) other; + return feedbackToUser.equals(otherCommandResult.feedbackToUser) + && type == otherCommandResult.getType(); + } + + @Override + public int hashCode() { + return Objects.hash(feedbackToUser, type); + } + +} diff --git a/src/main/java/seedu/knowitall/logic/commands/DeleteCommand.java b/src/main/java/seedu/knowitall/logic/commands/DeleteCommand.java new file mode 100755 index 000000000000..58d26944a277 --- /dev/null +++ b/src/main/java/seedu/knowitall/logic/commands/DeleteCommand.java @@ -0,0 +1,61 @@ +package seedu.knowitall.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.knowitall.commons.core.Messages; +import seedu.knowitall.commons.core.index.Index; +import seedu.knowitall.logic.CommandHistory; +import seedu.knowitall.logic.commands.exceptions.CommandException; +import seedu.knowitall.model.Model; +import seedu.knowitall.model.Model.State; +import seedu.knowitall.model.card.Card; + +/** + * Deletes a card identified using it's displayed index from the card folder. + */ +public class DeleteCommand extends Command { + + public static final String COMMAND_WORD = "delete"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the card identified by the index number used in the displayed card list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_CARD_SUCCESS = "Deleted Card: %1$s"; + + private final Index targetIndex; + + public DeleteCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + if (model.getState() != State.IN_FOLDER) { + throw new CommandException(Messages.MESSAGE_INVALID_COMMAND_OUTSIDE_FOLDER); + } + + List lastShownList = model.getActiveFilteredCards(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_CARD_DISPLAYED_INDEX); + } + + Card cardToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteCard(cardToDelete); + model.commitActiveCardFolder(); + return new CommandResult(String.format(MESSAGE_DELETE_CARD_SUCCESS, cardToDelete)); + } + + @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/knowitall/logic/commands/DeleteFolderCommand.java b/src/main/java/seedu/knowitall/logic/commands/DeleteFolderCommand.java new file mode 100755 index 000000000000..b659c1a74063 --- /dev/null +++ b/src/main/java/seedu/knowitall/logic/commands/DeleteFolderCommand.java @@ -0,0 +1,61 @@ +package seedu.knowitall.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.knowitall.commons.core.Messages; +import seedu.knowitall.commons.core.index.Index; +import seedu.knowitall.logic.CommandHistory; +import seedu.knowitall.logic.commands.exceptions.CommandException; +import seedu.knowitall.model.Model; +import seedu.knowitall.model.Model.State; +import seedu.knowitall.model.ReadOnlyCardFolder; + +/** + * Deletes a folder identified using it's displayed index from the home directory. + */ +public class DeleteFolderCommand extends Command { + + public static final String COMMAND_WORD = "deletefolder"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the card folder identified by the index number used in the displayed list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_FOLDER_SUCCESS = "Deleted Card Folder: %1$s"; + + private final Index targetIndex; + + public DeleteFolderCommand(Index targetIndex) { + requireNonNull(targetIndex); + + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + if (model.getState() != State.IN_HOMEDIR) { + throw new CommandException(Messages.MESSAGE_INVALID_COMMAND_INSIDE_FOLDER); + } + List cardFolderList = model.getCardFolders(); + + if (targetIndex.getZeroBased() >= cardFolderList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_FOLDER_DISPLAYED_INDEX); + } + + ReadOnlyCardFolder cardFolderToDelete = cardFolderList.get(targetIndex.getZeroBased()); + + model.deleteFolder(targetIndex.getZeroBased()); + return new CommandResult(String.format(MESSAGE_DELETE_FOLDER_SUCCESS, cardFolderToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteFolderCommand // instanceof handles nulls + && targetIndex.equals(((DeleteFolderCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/knowitall/logic/commands/EditCommand.java b/src/main/java/seedu/knowitall/logic/commands/EditCommand.java new file mode 100755 index 000000000000..18d1095ff2a4 --- /dev/null +++ b/src/main/java/seedu/knowitall/logic/commands/EditCommand.java @@ -0,0 +1,236 @@ +package seedu.knowitall.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.knowitall.logic.parser.CliSyntax.PREFIX_ANSWER; +import static seedu.knowitall.logic.parser.CliSyntax.PREFIX_HINT; +import static seedu.knowitall.logic.parser.CliSyntax.PREFIX_OPTION; +import static seedu.knowitall.logic.parser.CliSyntax.PREFIX_QUESTION; +import static seedu.knowitall.model.Model.PREDICATE_SHOW_ALL_CARDS; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.knowitall.commons.core.Messages; +import seedu.knowitall.commons.core.index.Index; +import seedu.knowitall.commons.util.CollectionUtil; +import seedu.knowitall.logic.CommandHistory; +import seedu.knowitall.logic.commands.exceptions.CommandException; +import seedu.knowitall.model.Model; +import seedu.knowitall.model.Model.State; +import seedu.knowitall.model.card.Answer; +import seedu.knowitall.model.card.Card; +import seedu.knowitall.model.card.Option; +import seedu.knowitall.model.card.Question; +import seedu.knowitall.model.card.Score; +import seedu.knowitall.model.hint.Hint; + +/** + * Edits the details of an existing card in the card folder. + */ +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 card identified " + + "by the index number used in the displayed card list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_QUESTION + "QUESTION] " + + "[" + PREFIX_ANSWER + "ANSWER] " + + "[" + PREFIX_OPTION + "OPTION]... " + + "[" + PREFIX_HINT + "HINT]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_ANSWER + "91234567 "; + + public static final String MESSAGE_EDIT_CARD_SUCCESS = "Edited Card: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_CARD = "This card already exists in the card folder."; + + private final Index index; + private final EditCardDescriptor editCardDescriptor; + + /** + * @param index of the card in the filtered card list to edit + * @param editCardDescriptor details to edit the card with + */ + public EditCommand(Index index, EditCardDescriptor editCardDescriptor) { + requireNonNull(index); + requireNonNull(editCardDescriptor); + + this.index = index; + this.editCardDescriptor = new EditCardDescriptor(editCardDescriptor); + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + if (model.getState() != State.IN_FOLDER) { + throw new CommandException(Messages.MESSAGE_INVALID_COMMAND_OUTSIDE_FOLDER); + } + + List lastShownList = model.getActiveFilteredCards(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_CARD_DISPLAYED_INDEX); + } + + Card cardToEdit = lastShownList.get(index.getZeroBased()); + Card editedCard = createEditedCard(cardToEdit, editCardDescriptor); + + if (!cardToEdit.isSameCard(editedCard) && model.hasCard(editedCard)) { + throw new CommandException(MESSAGE_DUPLICATE_CARD); + } + + model.setCard(cardToEdit, editedCard); + model.updateFilteredCard(PREDICATE_SHOW_ALL_CARDS); + model.commitActiveCardFolder(); + return new CommandResult(String.format(MESSAGE_EDIT_CARD_SUCCESS, editedCard)); + } + + /** + * Creates and returns a {@code Card} with the details of {@code cardToEdit} + * edited with {@code editCardDescriptor}. + */ + private static Card createEditedCard(Card cardToEdit, EditCardDescriptor editCardDescriptor) + throws CommandException { + assert cardToEdit != null; + + Question updatedQuestion = editCardDescriptor.getQuestion().orElse(cardToEdit.getQuestion()); + Answer updatedAnswer = editCardDescriptor.getAnswer().orElse(cardToEdit.getAnswer()); + // Score cannot be edited, so copy original + Score originalScore = cardToEdit.getScore(); + Set