diff --git a/.gitignore b/.gitignore index 2873e189e1..3c4103bf07 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ /out/ /*.iml +# VSCode setting files +.vscode/ + # Gradle build files /.gradle/ /build/ @@ -15,3 +18,4 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT +flashcardList.txt diff --git a/build.gradle b/build.gradle index d5e548e85f..fd935b8b8a 100644 --- a/build.gradle +++ b/build.gradle @@ -29,11 +29,11 @@ test { } application { - mainClass = "seedu.duke.Duke" + mainClass = "com.clanki.Clanki" } shadowJar { - archiveBaseName = "duke" + archiveBaseName = "clanki" archiveClassifier = null } @@ -41,6 +41,7 @@ checkstyle { toolVersion = '10.2' } -run{ +run { standardInput = System.in + enableAssertions = true } diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..a21a07b032 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,9 @@ -# About us +# About Us -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +| Display | Name | GitHub Profile | Portfolio | +|:--------------------------------------------------------------------------------------------|:-------------|:------------------------------------------|:---------------------------------| +| ![](https://avatars.githubusercontent.com/u/16986946?v=4&s=100) | Kong Dehao | [GitHub](https://github.com/kdh3799) | [Portfolio](./team/kdh3799) | +| ![](https://avatars.githubusercontent.com/u/44609036?v=4&s=100) | Vu Van Dung | [GitHub](https://github.com/joulev) | [Portfolio](./team/joulev) | +| | Song Zijin | [GitHub](https://github.com/SongZijin) | [Portfolio](./team/songzijin) | +| ![](https://avatars.githubusercontent.com/u/72350236?v=4&s=100) | Moses Lee | [GitHub](https://github.com/moseslee9012) | [Portfolio](./team/moseslee9012) | +| | Javienne Yeo | [GitHub](https://github.com/javienneyeo) | [Portfolio](./team/javienneyeo) | diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..ccf95eddd7 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,421 @@ # Developer Guide + + +* [Developer Guide](#developer-guide) + * [Acknowledgements](#acknowledgements) + * [Design & Implementation](#design--implementation) + * [Add Flashcard Feature](#add-flashcard-feature) + * [Current Implementation](#current-implementation) + * [Reason for Current Implementation](#reason-for-current-implementation) + * [Alternative Implementation](#alternative-implementation) + * [Delete Flashcard Feature](#delete-flashcard-feature) + * [Current Implementation](#current-implementation-1) + * [Reason for Current Implementation](#reason-for-current-implementation-1) + * [Alternative Implementation](#alternative-implementation-1) + * [Update Flashcard Feature](#update-flashcard-feature) + * [Current Implementation](#current-implementation-2) + * [Reason for Current Implementation](#reason-for-current-implementation-2) + * [Alternative Implementation](#alternative-implementation-2) + * [Review Flashcard Feature](#review-flashcard-feature) + * [Current Implementation](#current-implementation-3) + * [Reason for Current Implementation](#reason-for-current-implementation-3) + * [Alternative Implementation](#alternative-implementation-3) + * [Parser](#parser) + * [Current Implementation](#current-implementation-4) + * [`ParsedInput`](#parsedinput) + * [`Parser`](#parser-1) + * [Reason for Current Implementation](#reason-for-current-implementation-4) + * [Alternative Implementation](#alternative-implementation-4) + * [Storage Feature](#storage-feature) + * [Current Implementation](#current-implementation-5) + * [Reason for Current Implementation](#reason-for-current-implementation-5) + * [Alternative Implementation](#alternative-implementation-5) + * [Product Scope](#product-scope) + * [Target User Profile](#target-user-profile) + * [Value Proposition](#value-proposition) + * [User Stories](#user-stories) + + + ## Acknowledgements -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +- [addressbook-level2](https://github.com/se-edu/addressbook-level2) +- [Song Zijin's IP](https://github.com/SongZijin/ip) -## Design & implementation +## Design & Implementation -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +### Add Flashcard Feature +The image below shows a partial class diagram involving only the relevant +classes when an AddCommand is created and executed: +![AddFlashcardClassDiagram.png](umlDiagrams%2FAddFlashcardClassDiagram.png) -## Product scope -### Target user profile +#### Current Implementation -{Describe the target user profile} +The current add flashcard allows the user to add a flashcard to the list of +flashcards, it is implemented through the following steps: -### Value proposition +Step 1: The input of user is collected by `getUserCommand()` inside class `Ui`. -{Describe the value proposition: what problem does it solve?} +Step 2: The input string will be converted into a `Command` object by being +passed through `parseCommand(String userInput)` inside `Parser`. -## User Stories +In this case, an `AddCommand` will be created and returned, as shown in the +object diagram below: +![AddFlashcardObjectDiagram.png](umlDiagrams%2FAddFlashcardObjectDiagram.png) + +Step 3: The `execute()` function of `AddCommand` will run, calling +`addNewFlashcard(questionText, answerText)` of class `FlashcardList` to create +and add the new flashcard to the list. + +Then it will also call `printSuccessfulAddMessage(questionText, answerText)` of +class `Ui` to display text indicating the successful adding function to the +user. + +At this point, the adding process is completed and the program is ready to take +another command. + +The following sequence diagram show how the add operation works: +![AddFlashcardSequence.png](umlDiagrams%2FAddFlashcardSequence.png) + +#### Reason for Current Implementation + +Through using `AddCommand` class, which extends `Command` class it increases the +level of abstraction as the code can now perform the various commands on a class +level. + +Moreover, since the creating of new `Flashcard` of object and adding of the +newly created flashcard are both done in the same class as where the flashcards +are stored, this reduces coupling in the program as the `AddCommand` will not +have access to the inner structure of `FlashcardList`, which stores the list of +flashcards. + +#### Alternative Implementation + +- Alternative 1: Have the add command function directly in `FlashcardList` + + - Pros: Easy to implement + - Cons: Will require another function in another program to differentiate it + from other commands + +- Alternative 2: Have the constructor of `Flashcard` include adding the card to + list of flashcards + + - Pros: Simplifies code + - Cons: Will cause trouble when temporary flashcard (that need not be + stored) + are created + +### Delete Flashcard Feature + +The figure below shows a simple class diagram for the Delete Command. + +![DeleteFlashcardClassDiagram](umlDiagrams%2FDeleteFlashcardClassDiagram.png) + +#### Current Implementation + +The current delete flashcard allows the user to remove a flashcard from the list +of flashcards, it is implemented through the following steps: + +Step 1: The input of user is collected by `getUserCommand()` inside class `Ui`. + +Step 2: The input string will be converted into a `Command` object by being +passed through `parseCommand(String userInput)` inside `Parser`. + +In this case, a `DeleteCommnad` will be created and returned. + +Step 3: The `execute()` function of `DeleteCommand` will run, creating a copy of +the list of flashcards. Then `findFlashcards(flashcards, query)` is called to +find the flashcards with questions matching the query, before calling +`printFlashcardList(matchingFlashcards)` to display the found flashcards. + +User input is taken to get the index of the flashcard to be removed. +`deleteFlashcard` is called from class `flashcardList` to remove the flashcard +from the original list of flashcards. Finally `printSuccessfulDelete` is called +from class `Ui` to indicate a successful removal of the flashcard. + +The deletion process is now completed and the program will await another +command. + +An overview of how the Delete operation works is shown with the following +sequence diagram ![DeleteFlashcard.png](umlDiagrams%2FDeleteFlashcard.png) + +#### Reason for Current Implementation + +Through using `DeleteCommand` class, which extends `Command` class it increases +the level of abstraction as the code can now perform the various commands on a +class level. + +In order to minimise the time for users to search for the flashcard to delete, +they are able to first search for a sub-list of flashcards with matching +questions as the query. This method makes the deletion process simple even if +the user does not remember the index of the flashcard. + +#### Alternative Implementation + +- Alternative 1: Delete flashcard by index from the start + + - Pros: Easy to implement and simplifies code + - Cons: Cumbersome to delete if user forgets the flashcard's index and has + to + search through the whole list of flashcards. + +### Update Flashcard Feature + +#### Current Implementation + +The current update flashcard feature allows users search for a specific +flashcard and update the contents of this flashcard. It is implemented through +the following steps: + +Step 1: The input of user is collected by `getUserCommand()` inside class `Ui`. + +Step 2: The input string will be converted into a `Command` object by being +passed through `parseCommand(String userInput)` inside `Parser`. + +In this case, an `UpdateCommand` will be created and returned. + +Step 3: The `execute()` function of `UpdateCommand` will run `queryFlashcards(query)` +of `FlashcardList`, which will query for flashcards in the current deck that matches +the query inside either the question or answer and return an ArrayList of `Flashcard` +called `matchingFlashcards`. + +Step 4: Then, `printFlashCards(matchingFlashcards)` inside class `Ui` is called, which +prints all questions and answers of the list of flashcards, that matches the query, +to the console + +Step 5: Lastly, `runUpdateFlashcard(display)` is executed. This method prompts the user +for input and updates the specified flashcard based on that input. + +At this point, the update flashcard process is completed and the program is read +to take another command. + +An overview of how the Update command works is shown with the following sequence +diagram ![UpdateFlashcard.png](umlDiagrams%2FUpdateFlashcard.png) + +#### Reason for Current Implementation + +Implementing the update flashcard in an `UpdateCommand` class makes it easier +during the debugging process related to update flashcard feature alone as most +of the methods and attributes are within this `UpdateCommand` class. + +Furthermore, the UpdateCommand has a dedicated function, runUpdateFlashcard(display), +which handles the updating of the flashcard. This helps to ensure that the code +remains organized and easy to read, with the updating process separated from other +code. + +#### Alternative Implementation + +- Alternative 1: Instead of creating a new arrayList `matchingFlashcards` that + store flashcards containing the `query` and then printing the list of + flashcards, directly print the flashcards when there is a match with the query + + - Pros: Easier to implement + - Cons: Harder to track the total number of flashcards that has `query` and + will need to have another way to track the index of the matching + flashcards. + it will also be more confusing as the index of the user input is not + aligned + with the index of the arrayList that contains all the flashcards -|Version| As a ... | I want to ... | So that I can ...| -|--------|----------|---------------|------------------| -|v1.0|new user|see usage instructions|refer to them when I forget how to use the application| -|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list| +- Alternative 2: An alternative implementation could be to have the updating of + the flashcard handled directly in FlashcardList. -## Non-Functional Requirements + - Pros: Simple implementation and no need for another function in another + program to differentiate it from other commands + - Cons: May lead to increased coupling in the program, as the `UpdateCommand` + will have access to the inner structure of `FlashcardList` and this may make the + code more difficult to read and debug, as the updating process will be + combined with other code + +### Review Flashcard Feature -{Give non-functional requirements} +#### Current Implementation -## Glossary +The current review flashcard allows the user to review all the flashcards that +are due today or before, it is implemented through the following steps: -* *glossary item* - Definition +Step 1: The input of user is collected by `getUserCommand()` inside class `Ui`. -## Instructions for manual testing +Step 2: The input string will be converted into a `Command` object by being +passed through `parseCommand(String userInput)` inside `Paser`. + +In this case, an `ReviewCommand` will be created and returned. + +Step 3: The `execute()` function of `ReviewCommand` will run, calling +`getFlashCards()` of class `FlashcardList` to get the list of the flashcards. + +Then it will iterate through the `FlashcardList` and call the function +`isDueBeforeToday()` of class `Flashcard` to check if the flashcard is due by +today. + +If the flashcard is due by today, +`reviewCurrentFlashcard(Ui display, Flashcard flashcard)` of class +`ReviewCommand` will be called to review the card. + +First, the `Ui` will display the question of the current card by calling the +`getQuestion()` method of class `Flashcard`, and ask user if user is ready to +view the answer. After user enters any keyboard input, the answer of the current +card will be shown by calling the `getAnswer()` method of class `Flashcard`, and +`Ui` will ask the user if he/she has got the card correct. If the user inputs +"y", then the current `Flashcard` is considered to be cleared and +`updateDueDateAfterCorrectAnswer()` of `Flashcard` will be called to update its +`dueDate`. Then Clanki will let user review the next `Flashcard`. If the user +inputs "n", then the card is considered to be not cleared and +`updateDueDateAfterIncorrectAnswer()` will be called to update its `dueDate`. +Then Clanki will let user review the next `Flashcard`. This process will repeat +until all the `Flashcards` in the `FlashcardList` are iterated. + +After the whole `FlashcardList` has been iterated through, a message +congratulating the user that he/she has completed the reviewing task will be +displayed. + +At this point, the reviewing process is completed and the program is ready to +take another command. + +The following sequence diagram show how the review operation work: +![ReviewFlashcard-0.png](umlDiagrams%2FReviewFlashcard-1.png) + +#### Reason for Current Implementation + +Through using `ReivewCommand` class, which extends `Command` class it increases +the level of abstraction as the code can now perform the various commands on a +class level. + +Moreover, `ReviewCommand` only has access to the public methods of +`FlashcardList` and `Flashcard`, this reduces coupling in the program as the +`ReviewCommand` will not have access to the inner structure of `FlashcardList` +and `Flashcard`. + +#### Alternative Implementation + +- Alternative 1: Have the review command function directly in `FlashcardList` + + - Pros: Easy to implement + - Cons: Will require another function in another program to differentiate it + from other commands + +- Alternative 2: After entering the `ReviewCommand`, go back to `Clanki.run()` + and take further commands for review process + + - Pros: Simplifies code in `ReviewCommand` + - Cons: Will have to pass around a lot of parameters and variables + +### Parser + +#### Current Implementation + +The parser mostly relies on the `ParsedInput` class, which can parse any user +provided string input in the format of Windows command prompt commands +(`command body /opt-key opt-value`). + +##### `ParsedInput` + +Initiated with a string `input`, it splits the input to sections that are of +use. From there it splits each section further to a "title" (denoted with `=` +below) and a "body" (denoted with `-` below) part. + +``` +command blah blah /opt1 hello /opt2 world blah bleh +| Part 1 | | Part 2 | | Part 3 | +|=====| |-------| |==| |---| |==| |-------------| +``` + +Then these small subparts are grouped together to a format where the command +part of the command, the body part and the options can be retrieved +programmatically. + +The command and body can be read with `getCommand()` and `getBody()` +respectively. `getCommand()` is guaranteed to be non-null. + +The options can be read with `getOptionByName(optionKeyName)`. The reason we +don't have specific `getDate` or `getQuestion` command is because we don't know +what the user will input and what options we will require for each command. So +depending on the command, we retrieve the option accordingly with e.g. + +```java +"command blah blah /opt1 hello /opt2 world blah bleh" + getOptionByName("opt2") // -> "world blah bleh" + getOptionByName("opt3") // -> null +``` + +##### `Parser` + +This is now just a matter of wrapping `ParsedInput` with suitable error handling +and logic such that each command will be used to initiate a corresponding +command class (e.g. `AddCommand`), while errors are handled gracefully. + +#### Reason for Current Implementation + +We need an intuitive, safe and declarative way to parse the user input. +Alternative implementations that can only parse specific commands with specific +options are more imperative, less readable, less maintainable and overall just a +pain to handle. That's why the two classes are here. + +#### Alternative Implementation + +No. + +### Storage Feature + +#### Current Implementation + +The current storage feature triggers after every execution of command, updating +the flashcardList.txt file to be the came as what is stored in the +`FlashcardList` object. + +The entire feature consist of 3 parts, as shown in the class diagram below: +![StorageClassDiagm.png](umlDiagrams%2FStorageClassDiagm.png) + +1. `FlashcardListEncoder`: takes the list of flashcards from `FlashcardList` and + convert them to a list of strings, with heading to indicate the start of the + question, answer and deadline portion of a flashcard. +2. `FlashcardListDecoder`: takes a list of string in specific format (as defined + by `FLASHCARD_ARGS_FORMAT`) and decodes the string into an arrayList of + flashcards, discarding any string of incorrect format. +3. `StorageFile`: uses the encoder or decoder to save or load the current state + into or from a text file. + +The following sequence diagram show how the add operation works: +![StorageSequenceDiagram.png](umlDiagrams%2FStorageSequenceDiagram.png) + +#### Reason for Current Implementation + +By separating the decoder and encoder as separate classes, it allows the code +for the storage system to be more readable by others, allowing them to identify +and find the chunk of code for each function more easily, and possibly reuse the +functions if they deem necessary in future versions. + +#### Alternative Implementation + +- Alternative 1: Have all functions in one `Storage` class + + - Pros: Exceptions can be handled in the same place + - Cons: Will cause the code be less organised and readable + +## Product Scope + +### Target User Profile + +Students learning subjects that require a lot of memorisation (history, a new +language, etc.) + +### Value Proposition + +This application help users to better remember key points in their upcomming +tests by providing them a platform to read through and practice answering those +key learning points. + +## User Stories -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} +| Version | As a ... | I want to ... | So that I can ... | +|---------|-------------------------|---------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------| +| v1.0 | user | add a card to my flashcard collection | study it later on | +| v1.0 | user | delete any of my cards | prevent getting asked to review that card later when I am confident I have truly memorised the card | +| v1.0 | user | review the cards that are due today | remember them better | +| v1.0 | user | make changes to the q/a any cards I want | keep the info there always updated with what I want myself to memorise | +| v1.0 | user seeking efficiency | review the cards at an appropriate pace that is most efficient for memorisation | not waste time reviewing when I still remember the cards well | +| v2.0 | new user | see usage instructions | refer to them when I forget how to use the application | +| v2.0 | busy user | store the cards somewhere | revisit them next time I open the app | +| v2.0 | organised user | view a list of all currently stored flashcards | know what are the things I need to remember | diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..bd3448fc45 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,8 +1,10 @@ -# Duke +# Clanki -{Give product intro here} +Clanki is a command-line interface (CLI) application for managing flashcards. It +allows users to create, manage and study flashcards to assist them with +memorisation by using a technique called +[spaced repetition](https://en.wikipedia.org/wiki/Spaced_repetition). -Useful links: -* [User Guide](UserGuide.md) -* [Developer Guide](DeveloperGuide.md) -* [About Us](AboutUs.md) +- [User Guide](./UserGuide) +- [Developer Guide](./DeveloperGuide) +- [About Us](./AboutUs) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..9144859006 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,42 +1,312 @@ # User Guide -## Introduction +Clanki is a command-line interface (CLI) application for managing flashcards. It +allows users to create, manage and study flashcards to assist them with +memorisation by using a technique called +[spaced repetition](https://en.wikipedia.org/wiki/Spaced_repetition). -{Give a product intro} +## Quick start -## Quick Start +1. Ensure you have Java 11 or above installed. -{Give steps to get started quickly} +2. Download the latest version of Clanki from + [here](https://github.com/AY2223S2-CS2113-T15-4/tp/releases). -1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +3. Copy the file to the folder you want to use as the home folder for Clanki. -## Features +4. Open a command terminal, `cd` into the folder you put the jar file in, and + use the `java -jar clanki.jar` command to run the application. You should be + greeted with `Welcome to Clanki! Time to start studying!` after a few + seconds. -{Give detailed description of each feature} +5. Type the command in the command box and press Enter to execute it. Some + example commands you can try: -### Adding a todo: `todo` -Adds a new item to the list of todo items. + - `add /q what is the worst fruit? /a durian`: Add a flashcard with + Question: + What is the worst fruit? and Answer: Durian to the list of flashcards. -Format: `todo n/TODO_NAME d/DEADLINE` + - `review`: Go through the flashcards that are due today. -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. + - `bye`: Exit the app. -Example of usage: +6. Refer to the Features below for details of each command. -`todo n/Write the rest of the User Guide d/next week` +## Features -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +> Square brackets indicate optional sections of the syntax. -## FAQ +### Adding a flashcard -**Q**: How do I transfer my data to another computer? +``` +add /q QUESTION /a ANSWER +``` -**A**: {your answer here} +- `QUESTION` will be displayed when reviewing. For how the reviewing process + works, see "Review flashcards" section below. -## Command Summary +- Since a slash character (`/`) can be interpreted as a command option, both + `QUESTION` and `ANSWER` must not include (`/`). (In some of the times, `/` + might still work normally, that's just you being lucky.) -{Give a 'cheat sheet' of commands here} + - We might fix this in a future version. A far, far future version that + might + never come... -* Add todo `todo n/TODO_NAME d/DEADLINE` +- `/q QUESTION` and `/a ANSWER` can be arranged in any order. + +- The first letter of `QUESTION` and `ANSWER` are automatically capitalised. + +- If any options are duplicated, all but the last one are ignored. + +- The question and answer can include non-Latin characters such as Chinese + characters. However, your terminal might have trouble displaying it. If you + need such characters, find a terminal that supports it. A relatively modern + terminal (more modern than cmd.exe) should do, or buy a Mac since the default + Mac terminal does support it. + +#### Example + +``` +add /q What is the worst fruit? /a Durian +You have added the following card: +Q: What is the worst fruit? +A: Durian +``` + +``` +add /a 果物 /q What is the Japanese word for "fruit"? +You have added the following card: +Q: What is the Japanese word for "fruit"? +A: 果物 +``` + +### Review flashcards + +``` +review +``` + +- Flashcards that are due on the day will be displayed one after another, queued + in a secret order. + +- When the user has recalled the answer for a card, or has given up on doing so, + they can then press Enter. + +- The app will then show the answer and ask the user if they got it right. They + can then type `y`/`n` to indicate that they remembered the answer correctly, + or they couldn't remember the correct answer, respectively. + + - If `y` is indicated, the card is then set to a new due date in the + future.When the user has got it correct, the new date is set to a future + date according to a top-secret + [Spaced repetition](https://en.wikipedia.org/wiki/Spaced_repetition) + schedule that you can try to figure out if you want to explore mysteries. + - If `n` is indicated, the card is pushed back to the today's queue for + reviewing later, until the user can get it correct. + - If a command other than `y` or `n` is indicated, it will be regarded as an + invalid command, and then the user will be prompted again to enter a valid + answer. + +- Then the review session continues with the next card in the queue. + +#### Example + +``` +> review +There are 2 cards available for review today. +--- +Q: What is the worst fruit? (ENTER to view answer) +A: Durian +Did you get it right? (y/n) n +No worries, we will try again later today. +--- +Q: What is the Japanese word for "fruit"? (ENTER to view answer) +A: 果物 +Did you get it right? (y/n) y +Great, you got it right! +--- +Q: What is the worst fruit? (ENTER to view answer) +A: Durian +Did you get it right? (y/n) y +Great, you got it right! +--- +Congrats! You have reviewed all the flashcards due today! +``` + +### Update flashcards + +``` +update QUERY +``` + +- `QUERY` is part of the card's question or answer. It is not case sensitive. + +- A list of cards that contain this query will be listed, with an index assigned + to each. + +- The user can then type the index of the card they wish to update with the + following syntax + + ``` + INDEX [/q NEW_QUESTION] [/a NEW_ANSWER] + ``` + +- At least one of `/q` and `/a` must be provided. + +- Format of `NEW_QUESTION` and `NEW_ANSWER` is the same as in the `add` command + above. + +#### Example + +``` +update fruit +Found 2 card(s) with query "fruit": +[1] +Q: What is the worst fruit? +A: Durian +[2] +Q: What is the Japanese word for "fruit"? +A: 果物 +Which one do you want to update? +1 /q What is the best fruit? +Understood. The card has been updated to +Q: What is the best fruit? +A: Durian +``` + +### Delete a flashcard + +``` +delete QUERY +``` + +- `QUERY` can be part of the card's question or answer. + +- A list of cards that contain this query will be listed, with an index assigned + to each. + +- The user can then type the index of the card they wish to delete. The card + with that index (on that list) is then deleted. + +#### Example + +``` +delete fruit +Found 2 card(s) with query "fruit": +[1] +Q: What is the best fruit? +A: Durian +[2] +Q: What is the Japanese word for "fruit"? +A: 果物 +Which one do you want to delete? +2 +Got it. Deleted the flashcard at index 2 +``` + +### Delete all flashcards in the list + +``` +clear +``` + +Delete all the flashcards in the list. + +#### Example + +``` +clear +All flashcards have been deleted. +Your list of flashcards is now empty. +``` + +### List all flashcards + +``` +list all +``` + +Display the questions and answers for all the flashcards in the list that have +been added by the user, regardless of the date. + +#### Example + +``` +list all +Here is your list of flashcards: +[1] +Q: What is the biggest animal in the world +A: Antartic blue whale +[2] +Q: What are the best food for health +A: Lemons +[3] +Q: What colour is the sun +A: Red +``` + +### List flashcards with specific due date + +``` +list DUE_DATE +``` + +Display the questions and answers for all the flashcards in the list that has +the due date specified by the user + +- `DUE_DATE` has to be in the format `yyyy-mm-dd` + +``` +list 2023-04-15 +Here is your list of flashcards: +[1] +Q: What is the biggest animal in the world +A: Antartic blue whale +[2] +Q: What are the best food for health +A: Lemons +[3] +Q: What colour is the sun +A: Red +``` + +### Help menu + +``` +help +``` + +Display the list of possible commands the user can input. + +#### Example + +``` +help +The following are the commands you can use: +add Adds a flashcard to the current list of flashcards. + Parameters: add /q QUESTION /a ANSWER + Example: add /q What is the worst fruit? /a Durian +update Changes the content of flashcard's question or answer. + Parameters: update QUERY + Example: update fruit + Which flashcard do you want to update? 1 /q What is the best fruit? +delete Removes a flashcard with specified string. + Parameters: delete QUERY + Example: delete fruit +review Go through all flashcards that are due today. +list Lists out the questions and answers in the list of flashcards. + Parameters: list all (lists all flashcards) + Parameters: list DUE_DATE (list all flashcards with that specified due date) + Example: list 2023-05-04 +clear Deletes all the flashcards in the list. +bye Exit the program. +``` + +### Exit program + +``` +bye +``` + +Exit the program. Bye. diff --git a/docs/otherImages/ZijinProfile.jpg b/docs/otherImages/ZijinProfile.jpg new file mode 100644 index 0000000000..b866b96659 Binary files /dev/null and b/docs/otherImages/ZijinProfile.jpg differ diff --git a/docs/team/javienneyeo.md b/docs/team/javienneyeo.md new file mode 100644 index 0000000000..0e3659975a --- /dev/null +++ b/docs/team/javienneyeo.md @@ -0,0 +1,53 @@ +# Javienne Yeo's Project Portfolio Page + +## Overview of Product +- Clanki is a study tool to help students with content heavy subjects with a lot of memorization required. It is + able to add the questions and answers into flashcards, allowing users to review them when ready. + +### Summary of Contributions +- **New feature:** Added the ability to update the question, answer or due date of a flashcard + - What it does: The feature prints out a list of flashcards that contains the query in its question, + answer or due date. The feature than allows the user to choose which flashcard to update and how they + want it to be updated. The user is able to change the question, answer or due date part of the flashcard. + - Justification: This feature is required for users to make any changes to the flashcard. Without + this feature, the user would have to delete the flashcard and add a new flashcard in the event the + user wants to change the question, answer or due date. + - Highlights: Reduces the number of flashcards the user has to look through to find the specific flashcard + he/she wants to update as the feature prints out the list of flashcards that have that matching query. + In addition, the feature searches for the query in the question, answer and due date. JUnit test codes are + also written for this feature. +- **New feature:** Added the ability to list out the flashcards the user has added + - What it does: This feature allows the user to print out all flashcards that the user currently has or + print out all the flashcards that is due on a certain date + - Justification: This allows the user to have a broad overview of all the flashcards that he/she has + added to the list. This is to allow the user to check for whether they have added duplicates into the + list or to check whether the contents of the flashcards are all correct before reviewing them. + - Highlights: The user is able to view all the flashcards that are due on a certain day by entering + this date as an input. This is to allow the user to view all the flashcards with that due date to + revise before reviewing it using the review feature. JUnit test codes are also written for this feature. +- **New feature:** Added the ability to delete all flashcards in the current list of flashcards. + - What it does: Allows the user to remove all flashcards that have been added in the list to create a new + list of flashcards + - Justification: In the event the user has many flashcards in his/her list and wants to delete all of + it to start a new list, the user is able to use this function and would not need to delete the + flashcards one-by-one. It also allows the user to start a new list of flashcard with just one command. + +- **Code contributed:** + [RepoSense link](https://nus-cs2113-ay2223s2.github.io/tp-dashboard/?search=t15&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2023-02-17&tabOpen=true&tabType=authorship&zFR=false&tabAuthor=javienneyeo&tabRepo=AY2223S2-CS2113-T15-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +- **Contributions to UG:** + - Added section under `list flashcard` function + - Added section under `clear` function + - Edited parts of the section under `Update` function + +- **Contributions to DG:** + - Added implementation details for the `update` function. + + + + + + + + + diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index ab75b391b8..0000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,6 +0,0 @@ -# John Doe - Project Portfolio Page - -## Overview - - -### Summary of Contributions diff --git a/docs/team/joulev.md b/docs/team/joulev.md new file mode 100644 index 0000000000..de67fc7ece --- /dev/null +++ b/docs/team/joulev.md @@ -0,0 +1,57 @@ +# Vu Van Dung's Project Portfolio Page + +## Project: Clanki + +Clanki is a command line application one can use to store and update flashcards +to help them remember the relevant concepts. The user can interact with it by +typing code into their terminal. + +Given below are my contribution to the project. + +### Summary of Contributions + +- **New feature:** Parser + + - What it does: allows the app to intuitively and safely parse user input + - Justification: without it, imperative and specific implementation of the + parser for each command might be necessary, which will just be a grand + invitation for bugs + - Highlights: thanks to this feature, all places in the app where user input + is parsed can be handled declaratively. Since we take a lot of user inputs, + this is crucial for the app + +- **New feature:** Set up the ground work + + - What it does: not a user-facing feature, but this sets up Gradle, tests and + an overall file structure along with crucial classes (`Flashcard`, + `FlashcardList`, `FlashcardQueue`) that are used in all other places in the + app + - Justification: make development of the app easier + +- **Major contribution:** Improve other features by other members + + - What it does: other members' code might be inconsistent, might be + reinventing the wheels, might have hidden bugs, or overall just might be + bad; I fixed them to a level I'm at least not depressed when looking at the + code. + +- **Code contributed:** + [RepoSense link](https://nus-cs2113-ay2223s2.github.io/tp-dashboard/?search=joulev&sort=groupTitle%20dsc&sortWithin=title&since=2023-02-17&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=joulev&tabRepo=AY2223S2-CS2113-T15-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +- **Contributions to UG:** + + - Enhanced the user guide for all sections to make them more accurate and + closer to the actual behaviour and output of the app + - Added input restrictions (if any) to the UG + +- **Contributions to DG:** + + - Added details for the parser + +- Other contributions: + + - (Forced to) lead the project. I am basically the one who decided the + timeline and did most of the admin stuff + - Triaged incoming issues, opened tickets and assigned them to relevant + personnels + - Reviewed pull requests diff --git a/docs/team/kdh3799.md b/docs/team/kdh3799.md new file mode 100644 index 0000000000..992066e78a --- /dev/null +++ b/docs/team/kdh3799.md @@ -0,0 +1,31 @@ +# Kong Dehao - Project Portfolio Page + +## Overview: Project Clanki + +Clanki is a command line application to store flashcards used for +memorising information and concepts. The user can interact with +the app by typing commands into their terminal. + +### Summary of Contributions + +**New feature:** Added `review` function to remove flashcards + +- What it does: The `review` function allows users to review the flashcards that + are due today, depending on whether the user gets the answer correctly, the + new due date of the flashcards will be updated accordingly. +- Justification: This feature forms the basic building block of the program to + let the user review the flashcards he/she has added. +- Highlights: This feature involved multiple rounds of user interaction and make + corresponding updates based on user response. + +**Code contributed:** +[Reposense link](https://nus-cs2113-ay2223s2.github.io/tp-dashboard/?search=kdh3799) + +**Contributions to DG:** + +- Added implementation details and UML diagrams for + `review` command + +**Contributions to team-based tasks** + +- Added table of content for DG and rearranged the order of the sections of DG \ No newline at end of file diff --git a/docs/team/moseslee9012.md b/docs/team/moseslee9012.md new file mode 100644 index 0000000000..1508facbdd --- /dev/null +++ b/docs/team/moseslee9012.md @@ -0,0 +1,38 @@ +# Lee Zhi Zhong Moses - Project Portfolio Page + +## Overview: Project Clanki + +Clanki is a command line application to store flashcards used for +memorising information and concepts. The user can interact with +the app by typing commands into their terminal. + +### Summary of Contributions + +**New feature:** Added `delete` function to remove flashcards + +- What it does: The `delete` allows users to remove a specified + flashcard from the existing list. Typing "delete" will show the full + list of existing flashcards, and prompts the user for the index of + the flashcard to be deleted. The user can include a query, or keyword, + to find the relevant questions and narrow down the search list. +- Justification: This feature greatly improves the product as users can + easily remove the unnecessary flashcards and study only what is relevant. +- Highlights: This feature helps reduce the clutter of flashcards, either + from obsolete flashcards or cards that were added erroneously. + +**Code contributed:** +[Reposense link](https://nus-cs2113-ay2223s2.github.io/tp-dashboard/?search=moseslee9012&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2023-02-17&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +**Contributions to UG:** + +- Added section for "Delete Flashcards" + +**Contributions to DG:** + +- Added implementation details and UML diagrams for + `delete` command + +**Contributions to team-based tasks** + +- Helped refine details and correct small mistakes in the UG + and DG \ No newline at end of file diff --git a/docs/team/songzijin.md b/docs/team/songzijin.md new file mode 100644 index 0000000000..0ffeec0f01 --- /dev/null +++ b/docs/team/songzijin.md @@ -0,0 +1,52 @@ +# Song Zijin's Project Portfolio Page + +## Project: Clanki + +Clanki is a command line application one can use to store and update flashcards to help +them remember the relevant concepts. The user can interact with it by typing code into +their terminal. + +Given below are my contribution to the project. + +### Summary of Contributions + +- **New feature:** Added ability to add a new command + - What it does: allows the user to add a new flashcard with the specified question and answer text. + - Justification: This feature is one of the basic building block of the entire program, without it, no + other command on modifying the flashcards can work. + - Highlights: Since it is the initial step, building this feature included setting the groundwork in other basic + classes, such as creating the abstract Command class for other commands to use and FlashcardList class to + handle the flashcards that are created. JUnit test codes are also written for this feature. + - Credits: idea for the entire structure are reused from Song Zijin's individual project + (link [here](https://github.com/SongZijin/ip)), with many inspiration from + [addressbook-level2](https://github.com/se-edu/addressbook-level2). +- **New feature:** Added ability to save current state of all flashcards and load the state upon application start + - What it does: allows the user to store the previously recorded flashcards, + so that they can come back to review it in another day. + - Justification: This feature improves the product significantly, as the user can close the application + and come back in another day to review the flashcards they wish to learn. + - Highlights: The entire function consist of 3 parts, the encoder, decoder and a class that link the other two + together. The feature is also flexible when the stored data is corrupted, with the incorrectly formatted line + automatically deleted instead of crushing the whole program. + - Credits: the code is improved on the storage code from + [addressbook-level2](https://github.com/se-edu/addressbook-level2). +- **New feature:** Added a help menu + - What it does: provides the user some information about what are the commands they can use for this application. + - Justification: This feature improves the product, as the user is now able to use the product independently, + without the need to look up any user guide of the product. + +- **Code contributed:** + [RepoSense link](https://nus-cs2113-ay2223s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2023-02-17&tabOpen=true&tabType=authorship&zFR=false&tabAuthor=SongZijin&tabRepo=AY2223S2-CS2113-T15-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +- **Contributions to UG:** + - Added section under Quick Start. + - Provided initial draft to `add` function. + - Added documentation for `bye` and `help` functions. +- **Contributions to DG:** + - Added implementation details for the `add` function. + - Added implementation details for storage function. + - Added user stories + +- Other contributions: + - Reviewed pull requests, such as [#53](https://github.com/AY2223S2-CS2113-T15-4/tp/pull/53) + - Posted issues, such as [#61](https://github.com/AY2223S2-CS2113-T15-4/tp/issues/61) diff --git a/docs/umlDiagrams/AddFlashcard.puml b/docs/umlDiagrams/AddFlashcard.puml new file mode 100644 index 0000000000..358c4ed4eb --- /dev/null +++ b/docs/umlDiagrams/AddFlashcard.puml @@ -0,0 +1,52 @@ +@startuml +'https://plantuml.com/sequence-diagram + +participant "<> :Clanki" as Main +participant ":Ui" as Ui +participant ":Parser" as Parser +participant ":Add Command" as AddCommand +participant ":Flashcard List" as FlashcardList + +activate Main +Main -> Ui : User input +activate Ui + +Ui -> Main : userInput : String +deactivate Ui + +Main -> Parser : parseInput(userInput) +activate Parser + +create AddCommand +Parser -> AddCommand : AddCommand(question, answer) +activate AddCommand + +AddCommand --> Parser +deactivate AddCommand + +Parser -> Main : command : AddCommand +deactivate Parser + +Main -> AddCommand : execute() +activate AddCommand + +AddCommand -> FlashcardList : addNewFlashcard(question, answer) +activate FlashcardList + +FlashcardList --> AddCommand +deactivate FlashcardList + +AddCommand -> Ui : printSuccessfulAddMessage(question, answer) +activate Ui + + +Ui --> AddCommand +deactivate Ui + +AddCommand --> Main +deactivate AddCommand +destroy AddCommand + + + +@enduml \ No newline at end of file diff --git a/docs/umlDiagrams/AddFlashcardClassDiagram.png b/docs/umlDiagrams/AddFlashcardClassDiagram.png new file mode 100644 index 0000000000..481f48bf70 Binary files /dev/null and b/docs/umlDiagrams/AddFlashcardClassDiagram.png differ diff --git a/docs/umlDiagrams/AddFlashcardClassDiagram.puml b/docs/umlDiagrams/AddFlashcardClassDiagram.puml new file mode 100644 index 0000000000..c6457df7a7 --- /dev/null +++ b/docs/umlDiagrams/AddFlashcardClassDiagram.puml @@ -0,0 +1,20 @@ +@startuml +'https://plantuml.com/sequence-diagram +abstract Command +Command : execute(flashcardList: FlashcardList, display: Ui) : void +class AddCommand +AddCommand : execute(flashcardList: FlashcardList, display: Ui) : void +class Ui +Ui : printSuccessfulAddMessage() : void +class FlashcardList +FlashcardList : addNewFlashcard(questionText : String, answerText : String) : void +class Flashcard +class Parser +Parser : parseCommands (userInput : String) : Command + +Command <|-- AddCommand +AddCommand ..> Ui : calls > +AddCommand ..> FlashcardList : calls > +Parser ..> AddCommand : creates > +FlashcardList --> "*" Flashcard +@enduml \ No newline at end of file diff --git a/docs/umlDiagrams/AddFlashcardObjectDiagram.png b/docs/umlDiagrams/AddFlashcardObjectDiagram.png new file mode 100644 index 0000000000..941cf2db29 Binary files /dev/null and b/docs/umlDiagrams/AddFlashcardObjectDiagram.png differ diff --git a/docs/umlDiagrams/AddFlashcardObjectDiagram.puml b/docs/umlDiagrams/AddFlashcardObjectDiagram.puml new file mode 100644 index 0000000000..eba65be996 --- /dev/null +++ b/docs/umlDiagrams/AddFlashcardObjectDiagram.puml @@ -0,0 +1,9 @@ +@startuml +object ":Parser" as Parser +object "command :AddCommand" as AddCommand { + questionText :String + answerText :String +} + +Parser -> AddCommand : creates > +@enduml \ No newline at end of file diff --git a/docs/umlDiagrams/AddFlashcardSequence.png b/docs/umlDiagrams/AddFlashcardSequence.png new file mode 100644 index 0000000000..c20b3c8c66 Binary files /dev/null and b/docs/umlDiagrams/AddFlashcardSequence.png differ diff --git a/docs/umlDiagrams/DeleteFlashcard.png b/docs/umlDiagrams/DeleteFlashcard.png new file mode 100644 index 0000000000..3c9007ca58 Binary files /dev/null and b/docs/umlDiagrams/DeleteFlashcard.png differ diff --git a/docs/umlDiagrams/DeleteFlashcard.puml b/docs/umlDiagrams/DeleteFlashcard.puml new file mode 100644 index 0000000000..5497354995 --- /dev/null +++ b/docs/umlDiagrams/DeleteFlashcard.puml @@ -0,0 +1,63 @@ +@startuml +'https://plantuml.com/class-diagram + +!define LOGIC_COLOR #3333C4 +!define LOGIC_COLOR_T1 #7777DB +!define LOGIC_COLOR_T2 #5252CE +!define LOGIC_COLOR_T3 #1616B0 +!define LOGIC_COLOR_T4 #101086 + +participant "<> :Clanki" as Main +participant ":Ui" as Ui +participant ":Parser" as Parser +participant ":Delete Command" as DeleteCommand +participant ":Flashcard List" as FlashcardList + +activate Main +Main -> Ui : User input +activate Ui + +Ui -> Main : userInput : String +deactivate Ui + +Main -> Parser : parseInput(userInput) +activate Parser + +create DeleteCommand +Parser -> DeleteCommand :DeleteCommand(query) +activate DeleteCommand + +DeleteCommand -> Parser +deactivate DeleteCommand + +Parser -> Main : command : DeleteCommand +deactivate Parser + +Main -> DeleteCommand : execute() +activate DeleteCommand + +DeleteCommand -> FlashcardList : queryFlashcards(query) +activate FlashcardList + +FlashcardList -> DeleteCommand : matchingFlashcards : ArrayList +deactivate FlashcardList + +DeleteCommand -> FlashcardList : getFlashcards() +activate FlashcardList + +FlashcardList -> DeleteCommand +deactivate FlashcardList + +DeleteCommand -> Ui : printSuccessfulDelete(index) +activate Ui + +Ui -> Main : Display Message +Ui -> DeleteCommand +deactivate Ui + +DeleteCommand -> Main +deactivate DeleteCommand + + + +@enduml \ No newline at end of file diff --git a/docs/umlDiagrams/DeleteFlashcardClassDiagram.png b/docs/umlDiagrams/DeleteFlashcardClassDiagram.png new file mode 100644 index 0000000000..487015fd26 Binary files /dev/null and b/docs/umlDiagrams/DeleteFlashcardClassDiagram.png differ diff --git a/docs/umlDiagrams/DeleteFlashcardClassDiagram.puml b/docs/umlDiagrams/DeleteFlashcardClassDiagram.puml new file mode 100644 index 0000000000..95378d6ba7 --- /dev/null +++ b/docs/umlDiagrams/DeleteFlashcardClassDiagram.puml @@ -0,0 +1,23 @@ +@startuml + +'https://plantuml.com/class-diagram +abstract Command +Command : execute(flashcardList: FlashcardList, display: Ui) : void +class DeleteCommand +DeleteCommand : execute(flashcardList: FlashcardList, display: Ui) : void +class Ui +Ui : printSuccessfulDeleteMessage() : void +class FlashcardList +FlashcardList : queryFlashcards(query : String) : ArrayList +FlashcardList : deleteFlashcard(index : int) : void +class Flashcard +class Parser +Parser : parseCommands (userInput : String) : Command + +Command <|-- DeleteCommand +DeleteCommand ..> Ui : calls > +DeleteCommand ..> FlashcardList : calls > +Parser ..> DeleteCommand : creates > +FlashcardList --> "*" Flashcard + +@enduml \ No newline at end of file diff --git a/docs/umlDiagrams/ReviewFlashcard-1.png b/docs/umlDiagrams/ReviewFlashcard-1.png new file mode 100644 index 0000000000..9d7d65d404 Binary files /dev/null and b/docs/umlDiagrams/ReviewFlashcard-1.png differ diff --git a/docs/umlDiagrams/ReviewFlashcard.puml b/docs/umlDiagrams/ReviewFlashcard.puml new file mode 100644 index 0000000000..9bef9ccc3e --- /dev/null +++ b/docs/umlDiagrams/ReviewFlashcard.puml @@ -0,0 +1,92 @@ +@startuml +'https://plantuml.com/sequence-diagram + +participant "<> :Clanki" as Main +participant ":Ui" as Ui +participant ":Parser" as Parser +participant ":Review Command" as ReviewCommand +participant ":Flashcard List" as FlashcardList +participant ":Flashcard" as Flashcard +participant ":FlashcardQueue" as FlashcardQueue + +activate Main +Main -> Ui : User input +activate Ui + +Ui -> Main : userInput : String + +Main -> Parser : parseInput(userInput) +activate Parser + +create ReviewCommand +Parser -> ReviewCommand : ReviewCommand() +activate ReviewCommand + +ReviewCommand --> Parser +deactivate ReviewCommand + +Parser --> Main : command : ReviewCommand +deactivate Parser + +Main -> ReviewCommand : execute() +activate ReviewCommand + +ReviewCommand -> FlashcardList : getFlashCards() +activate FlashcardList + +create FlashcardQueue +FlashcardList -> FlashcardQueue : FlashcardQueue() +activate FlashcardQueue +FlashcardQueue --> FlashcardList +FlashcardList --> ReviewCommand +deactivate FlashcardList + +alt flashcardQueue.isEmpty() + ReviewCommand -> Ui : printlnSeveralStrings("There are no cards available for review today.") + Ui -> ReviewCommand +else + ReviewCommand -> Ui : printlnSeveralStrings("There are %d cards available for review today.", flashcardQueue.size()) + Ui -> FlashcardQueue : size() + FlashcardQueue --> Ui : String + loop while (!flashcardQueue.isEmpty()) + + ReviewCommand -> FlashcardQueue : popFlashcard() + FlashcardQueue --> ReviewCommand : Flashcard + activate Flashcard + ReviewCommand -> Flashcard : getQuestion() + Flashcard --> ReviewCommand + ReviewCommand -> Ui : getUserCommand() + Ui --> ReviewCommand + ReviewCommand -> Flashcard : getAnswer() + Flashcard --> ReviewCommand + ReviewCommand -> Ui : getUserCommand() + Ui --> ReviewCommand : answerIfCorrect : String + + alt answerIfCorrect.equals("y") + ReviewCommand -> Ui :printlnSeveralStrings("Great, you got it right!") + Ui --> ReviewCommand + ReviewCommand -> Flashcard : updateDueDateAfterCorrectAnswer() + Flashcard --> ReviewCommand + else answerIfCorrect.equals("n") + ReviewCommand -> Ui : printlnSeveralStrings("No worries, we will try again later today.") + Ui --> ReviewCommand + ReviewCommand -> Flashcard : updateDueDateAfterIncorrectAnswer() + Flashcard --> ReviewCommand + ReviewCommand -> FlashcardQueue : pushFlashcard(currentFlashcard) + FlashcardQueue -> ReviewCommand + else + ReviewCommand -> Ui : printInvalidInput() + Ui --> ReviewCommand + end + deactivate Flashcard + end + destroy FlashcardQueue +end + +ReviewCommand -> Ui : printlnSeveralStrings("Congrats! You have reviewed all the flashcards due today!") +Ui --> ReviewCommand +deactivate Ui +ReviewCommand -> Main +deactivate ReviewCommand + +@enduml \ No newline at end of file diff --git a/docs/umlDiagrams/StorageClassDiagm.png b/docs/umlDiagrams/StorageClassDiagm.png new file mode 100644 index 0000000000..0cf7869b72 Binary files /dev/null and b/docs/umlDiagrams/StorageClassDiagm.png differ diff --git a/docs/umlDiagrams/StorageClassDiagm.puml b/docs/umlDiagrams/StorageClassDiagm.puml new file mode 100644 index 0000000000..b44fb5076e --- /dev/null +++ b/docs/umlDiagrams/StorageClassDiagm.puml @@ -0,0 +1,27 @@ +@startuml +class StorageFile { + + path : Path + + StorageFile() + + save( flashcardList : FlashcardList) : void + + load() : ArrayList +} +class FlashcardEncoder { + + {static} encodeFlashcardList( toSave : FlashcardList ) : List + - {static} encodeFlashcardToString( flashcard : Flashcard) : String +} +class FlashcardDecoder { + + {static} decodeFlashcardList( encodedFlashcardList : List) : ArrayList + - {static} decodeFlashcardFromString ( encodedFlashcard : String) : Flashcard +} +class Clanki { + - storageFile : StorageFile + + Clanki() + + {static} main (args : String[]) : void + + run() : void +} + +Clanki *--> "1" StorageFile : controls > +StorageFile --> FlashcardDecoder : uses > +StorageFile --> FlashcardEncoder : uses > + +@enduml \ No newline at end of file diff --git a/docs/umlDiagrams/StorageSequenceDiagram.png b/docs/umlDiagrams/StorageSequenceDiagram.png new file mode 100644 index 0000000000..2c2cd27533 Binary files /dev/null and b/docs/umlDiagrams/StorageSequenceDiagram.png differ diff --git a/docs/umlDiagrams/StorageSequenceDiagram.puml b/docs/umlDiagrams/StorageSequenceDiagram.puml new file mode 100644 index 0000000000..ac19f68d33 --- /dev/null +++ b/docs/umlDiagrams/StorageSequenceDiagram.puml @@ -0,0 +1,65 @@ +@startuml +participant "<> :Clanki" as Main +participant "storageFile :StorageFile" as Storage +participant "<> :FlashcardListDecoder" as Decoder +participant "<> :FlashcardListEncoder" as Encoder + + +activate Main +create Storage +Main -> Storage : StorageFile() +activate Storage +Storage --> Main +deactivate Storage + +Main -> Storage : load() +activate Storage +Storage -> Decoder : decodeFlashcardList(Files.readAllLines(path) :List) +activate Decoder +loop for all String in List + Decoder -> Decoder : decodeFlashcardFromString(encodedFlashcard :String) + activate Decoder + opt String does not match pattern + Decoder -> Decoder : :StorageOperationException(ENCODED_FLASHCARD_IN_INVALID_FORMAT_UNABLE_TO_DECODE); + end + Decoder -> Decoder : :Flashcard + deactivate Decoder +end +Decoder -> Storage : flashcards : ArrayList +deactivate Decoder +Storage -> Main : flashcards : ArrayList +deactivate Storage + +Main -> Main : run() +activate Main +loop command instanceof ByeCommand + <- Main : getUserCommand() + -> Main : inputText :String + <- Main : parseCommand(inputText) + -> Main : command :Command + <- Main : execute() + --> Main + Main -> Storage : save() + activate Storage + Storage -> Encoder : encodeFlashcardList(flashcardList :FlashcardList) + activate Encoder + loop for all Flashcard in ArrayList from flashcardlist + Encoder -> Encoder : decodeFlashcardFromString(encodedFlashcard :String) + activate Encoder + Encoder -> Encoder : :String + deactivate Encoder + end + Encoder -> Storage : encodedFlashcardList :FlashcardList + deactivate Encoder + Storage --> Main + deactivate Storage +end +Main --> Main +deactivate Main + + + + + + +@enduml \ No newline at end of file diff --git a/docs/umlDiagrams/UpdateFlashcard.png b/docs/umlDiagrams/UpdateFlashcard.png new file mode 100644 index 0000000000..6cf1c4214d Binary files /dev/null and b/docs/umlDiagrams/UpdateFlashcard.png differ diff --git a/docs/umlDiagrams/UpdateFlashcard.puml b/docs/umlDiagrams/UpdateFlashcard.puml new file mode 100644 index 0000000000..2d5f18aa3d --- /dev/null +++ b/docs/umlDiagrams/UpdateFlashcard.puml @@ -0,0 +1,70 @@ +@startuml +'https://plantuml.com/sequence-diagram + +title UpdateCommand Sequence Diagram + +participant "<> :Clanki" as Main +participant ":UpdateCommand" as UpdateCommand +participant ":Ui" as Ui +participant ":Parser" as Parser +participant ":Flashcard" as Flashcard +participant ":Flashcard List" as FlashcardList +participant ":ParsedInput" as ParsedInput + +Main -> Ui: User input +activate Main +activate Ui + +Ui -> Main : userInput :String +deactivate Ui + +Main -> Parser : parseInput(userInput) +activate Parser + +Parser -> UpdateCommand: UpdateCommand(query) +activate UpdateCommand + +UpdateCommand -> Parser +deactivate UpdateCommand + +Parser -> Main : command : UpdateCommand +deactivate Parser + +Main -> UpdateCommand : execute() +activate UpdateCommand + +UpdateCommand -> FlashcardList: queryFlashcards(query) +activate FlashcardList + +FlashcardList -> UpdateCommand : matchingFlashcards +deactivate FlashcardList + +UpdateCommand -> Ui: printFlashCards(matchingFlashcards) +activate Ui + +Ui -> UpdateCommand +deactivate Ui + +loop while not done + UpdateCommand -> Ui: getUserCommand() + activate Ui + + Ui -> ParsedInput: ParsedInput(display.getUserCommand()) + deactivate Ui + activate ParsedInput + + ParsedInput -> UpdateCommand + deactivate ParsedInput + + UpdateCommand -> Ui : printSuccessfulUpdateMessage(currentFlashcard) + activate Ui + + Ui -> UpdateCommand + deactivate Ui + + UpdateCommand -> Main + deactivate UpdateCommand + destroy UpdateCommand + +end +@enduml \ No newline at end of file diff --git a/src/main/java/com/clanki/Clanki.java b/src/main/java/com/clanki/Clanki.java new file mode 100644 index 0000000000..c4c8b4462c --- /dev/null +++ b/src/main/java/com/clanki/Clanki.java @@ -0,0 +1,53 @@ +package com.clanki; + +import com.clanki.commands.ByeCommand; +import com.clanki.commands.Command; +import com.clanki.exceptions.InvalidStorageFilePathException; +import com.clanki.exceptions.StorageOperationException; +import com.clanki.objects.FlashcardList; +import com.clanki.parser.Parser; +import com.clanki.storage.StorageFile; +import com.clanki.ui.Ui; + +public class Clanki { + private Ui ui; + private FlashcardList flashcardList; + private StorageFile storageFile; + + public Clanki() { + try { + this.ui = new Ui(); + this.storageFile = new StorageFile(); + this.flashcardList = new FlashcardList(storageFile.load()); + } catch (InvalidStorageFilePathException | StorageOperationException e) { + System.out.println(e.getMessage()); + } + } + + public static void main(String[] args) { + new Clanki().run(); + } + + /** + * Function that loop the process of getting an user input, processing the input + * and execute the command, until a ByeCommand is inputted. + */ + public void run() { + ui.printWelcomeMessage(); + ui.printSeparationLine(); + while (true) { + String inputText = ui.getUserCommand(); + Command command = Parser.parseCommand(inputText); + command.execute(flashcardList, ui); + try { + storageFile.save(flashcardList); + } catch (StorageOperationException e) { + System.out.println(e.getMessage()); + } + if (command instanceof ByeCommand) { + return; + } + ui.printSeparationLine(); + } + } +} diff --git a/src/main/java/com/clanki/commands/AddCommand.java b/src/main/java/com/clanki/commands/AddCommand.java new file mode 100644 index 0000000000..50e6fe6dcc --- /dev/null +++ b/src/main/java/com/clanki/commands/AddCommand.java @@ -0,0 +1,34 @@ +package com.clanki.commands; + +import com.clanki.objects.FlashcardList; +import com.clanki.ui.Ui; + +/** + * A type of command that will instruct to add a new element into the ArrayList + * of flashcards. + */ +public class AddCommand extends Command { + String questionText; + String answerText; + + public AddCommand(String questionText, String answerText) { + assert !questionText.isEmpty() : "question text should not be empty"; + this.questionText = questionText.substring(0, 1).toUpperCase() + questionText.substring(1); + assert !answerText.isEmpty() : "answer text should not be empty"; + this.answerText = answerText.substring(0, 1).toUpperCase() + answerText.substring(1); + } + + /** + * For testing purposes only. + */ + @Override + public String toString() { + return "Question to add: " + questionText + " Answer: " + answerText; + } + + @Override + public void execute(FlashcardList flashcardList, Ui display) { + flashcardList.addNewFlashcard(questionText, answerText); + display.printSuccessfulAddMessage(questionText, answerText); + } +} diff --git a/src/main/java/com/clanki/commands/ByeCommand.java b/src/main/java/com/clanki/commands/ByeCommand.java new file mode 100644 index 0000000000..cef9a57ee4 --- /dev/null +++ b/src/main/java/com/clanki/commands/ByeCommand.java @@ -0,0 +1,14 @@ +package com.clanki.commands; + +import com.clanki.objects.FlashcardList; +import com.clanki.ui.Ui; + +/** + * A type of command that will indicate that the program is ready to close. + */ +public class ByeCommand extends Command { + @Override + public void execute(FlashcardList flashcardList, Ui display) { + display.printByeMessage(); + } +} diff --git a/src/main/java/com/clanki/commands/ClearCommand.java b/src/main/java/com/clanki/commands/ClearCommand.java new file mode 100644 index 0000000000..e062673aed --- /dev/null +++ b/src/main/java/com/clanki/commands/ClearCommand.java @@ -0,0 +1,16 @@ +package com.clanki.commands; + +import com.clanki.objects.FlashcardList; +import com.clanki.ui.Ui; + +/** + * The type of command when the user wishes to clear all stored flashcards. + */ +public class ClearCommand extends Command { + + @Override + public void execute(FlashcardList flashcardList, Ui display) { + flashcardList.deleteAllFlashcards(); + display.printClearMessage(); + } +} diff --git a/src/main/java/com/clanki/commands/Command.java b/src/main/java/com/clanki/commands/Command.java new file mode 100644 index 0000000000..1e843c8316 --- /dev/null +++ b/src/main/java/com/clanki/commands/Command.java @@ -0,0 +1,12 @@ +package com.clanki.commands; + +import com.clanki.objects.FlashcardList; +import com.clanki.ui.Ui; + +/** + * Represents a command that is instructed for the program to conduct. A Command + * Object specifies what the type of command is with its subclasses. + */ +public abstract class Command { + public abstract void execute(FlashcardList flashcardList, Ui display); +} diff --git a/src/main/java/com/clanki/commands/DeleteCommand.java b/src/main/java/com/clanki/commands/DeleteCommand.java new file mode 100644 index 0000000000..66494652e7 --- /dev/null +++ b/src/main/java/com/clanki/commands/DeleteCommand.java @@ -0,0 +1,54 @@ +package com.clanki.commands; + +import com.clanki.objects.Flashcard; +import com.clanki.objects.FlashcardList; +import com.clanki.ui.Ui; + +import java.util.ArrayList; + +/** + * A command that removes an element from the ArrayList of flashcards. + */ +public class DeleteCommand extends Command { + ArrayList matchingFlashcards; + String query; + + public DeleteCommand(String query) { + this.matchingFlashcards = new ArrayList<>(); + this.query = query; + } + + @Override + public void execute(FlashcardList flashcardList, Ui display) { + matchingFlashcards = flashcardList.queryFlashcards(query); + + if (matchingFlashcards.size() == 0) { + System.out.printf("Sorry! No flashcards matching \"%s\" was found. Please try again.\n", + query); + return; + } + + System.out.printf("Found %d card(s) with query \"%s\":\n", matchingFlashcards.size(), + query); + display.printFlashCards(matchingFlashcards); + System.out.println("Which one do you want to delete?"); + + int index = -1; + while (index == -1) { + try { + index = Integer.parseInt(display.getUserCommand()); + if (index < 1 || index > matchingFlashcards.size()) { + index = -1; + throw new Exception(); + } + } catch (Exception e) { + System.out.println("Please enter a valid index value."); + } + } + + ArrayList flashcards = flashcardList.getFlashCards(); + int actualIndex = flashcards.indexOf(matchingFlashcards.get(index - 1)); + flashcardList.deleteFlashcard(actualIndex); + display.printSuccessfulDelete(index); + } +} diff --git a/src/main/java/com/clanki/commands/HelpCommand.java b/src/main/java/com/clanki/commands/HelpCommand.java new file mode 100644 index 0000000000..b4136cb91b --- /dev/null +++ b/src/main/java/com/clanki/commands/HelpCommand.java @@ -0,0 +1,14 @@ +package com.clanki.commands; + +import com.clanki.objects.FlashcardList; +import com.clanki.ui.Ui; + +/** + * A type of command that indicate that user want to display help menu. + */ +public class HelpCommand extends Command { + @Override + public void execute(FlashcardList flashcardList, Ui display) { + display.printHelpMessage(); + } +} diff --git a/src/main/java/com/clanki/commands/ListCommand.java b/src/main/java/com/clanki/commands/ListCommand.java new file mode 100644 index 0000000000..15ee74781d --- /dev/null +++ b/src/main/java/com/clanki/commands/ListCommand.java @@ -0,0 +1,59 @@ +package com.clanki.commands; + +import com.clanki.objects.Flashcard; +import com.clanki.objects.FlashcardList; +import com.clanki.ui.Ui; + +import java.time.LocalDate; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; + +//@@author javienneYeo +public class ListCommand extends Command { + String input; + + public ListCommand(String input) { + super(); + this.input = input; + } + + private LocalDate convertDate(String dueDate) throws DateTimeParseException { + return LocalDate.parse(dueDate); + } + + private void printFlashcards(ArrayList flashcards, Ui display) { + if (flashcards.size() == 0) { + System.out.println("Your list of flashcards is empty."); + return; + } + System.out.println("Here is your list of flashcards:"); + display.printFlashCards(flashcards); + } + + public void printFlashcardsOnDate(ArrayList flashcards, LocalDate date, Ui display) { + ArrayList flashcardsOnDate = new ArrayList<>(); + for (int i = 0; i < flashcards.size(); i++) { + Flashcard currentFlashcard = flashcards.get(i); + if (currentFlashcard.getDueDate().equals(date)) { + flashcardsOnDate.add(currentFlashcard); + } + } + printFlashcards(flashcardsOnDate, display); + } + + @Override + public void execute(FlashcardList flashcardList, Ui display) { + ArrayList flashcards = flashcardList.getFlashCards(); + if (input.equals("all")) { + printFlashcards(flashcards, display); + return; + } + + try { + LocalDate dueDate = convertDate(input); + printFlashcardsOnDate(flashcards, dueDate, display); + } catch (DateTimeParseException e) { + System.out.println("Please enter the date in the format: yyyy-mm-dd"); + } + } +} diff --git a/src/main/java/com/clanki/commands/ReviewCommand.java b/src/main/java/com/clanki/commands/ReviewCommand.java new file mode 100644 index 0000000000..0646f88fa9 --- /dev/null +++ b/src/main/java/com/clanki/commands/ReviewCommand.java @@ -0,0 +1,71 @@ +package com.clanki.commands; + +import com.clanki.objects.Flashcard; +import com.clanki.objects.FlashcardList; +import com.clanki.objects.FlashcardQueue; +import com.clanki.ui.Ui; + +/** + * A type of command that will let user review the flashcards due today + */ +public class ReviewCommand extends Command { + + public static final String CORRECT_ANSWER_STRING = "y"; + public static final String INCORRECT_ANSWER_STRING = "n"; + public static final String LINE_SEPARATOR = "---"; + + /** + * review the next flashcard in the queue, and update the due date of the flashcard accordingly + * + * @param display Ui object used for user interfacing + * @param flashcardQueue Queue object storing flashcards to be reviewed + */ + private static void reviewNextFlashcard(Ui display, FlashcardQueue flashcardQueue) { + display.printlnSeveralStrings(LINE_SEPARATOR); + Flashcard currentFlashcard = flashcardQueue.popFlashcard(); + System.out.printf("Q: %s (ENTER to view answer)", currentFlashcard.getQuestion()); + display.getUserCommand(); + display.printlnSeveralStrings(String.format("A: %s", currentFlashcard.getAnswer())); + String answerIfCorrect; + System.out.print("Did you get it right? (y/n) "); + while (true) { + answerIfCorrect = display.getUserCommand().toLowerCase(); + if (answerIfCorrect.equals(CORRECT_ANSWER_STRING)) { + display.printlnSeveralStrings("Great, you got it right!"); + currentFlashcard.updateDueDateAfterCorrectAnswer(); + break; + } else if (answerIfCorrect.equals(INCORRECT_ANSWER_STRING)) { + display.printlnSeveralStrings("No worries, we will try again later today."); + currentFlashcard.updateDueDateAfterIncorrectAnswer(); + flashcardQueue.pushFlashcard(currentFlashcard); + break; + } else { + System.out.print("Please enter 'y' or 'n': (y/n) "); + } + } + } + + /** + * Review all the flashcards that are due today, and update their due dates accordingly + * + * @param flashcardList List of all flashcards, regardless of their due dates + * @param display Ui object used for user interfacing + */ + @Override + public void execute(FlashcardList flashcardList, Ui display) { + // Get a queue of flashcards that are due today + FlashcardQueue flashcardQueue = new FlashcardQueue(flashcardList.getFlashCards()); + + if (flashcardQueue.isEmpty()) { + display.printlnSeveralStrings("There are no cards available for review today."); + return; + } + display.printlnSeveralStrings(String + .format("There are %d cards available for review today.", flashcardQueue.size())); + while (!flashcardQueue.isEmpty()) { + reviewNextFlashcard(display, flashcardQueue); + } + display.printlnSeveralStrings(LINE_SEPARATOR, + "Congrats! You have reviewed all the flashcards due today!"); + } +} diff --git a/src/main/java/com/clanki/commands/UnknownCommand.java b/src/main/java/com/clanki/commands/UnknownCommand.java new file mode 100644 index 0000000000..832e3e0578 --- /dev/null +++ b/src/main/java/com/clanki/commands/UnknownCommand.java @@ -0,0 +1,14 @@ +package com.clanki.commands; + +import com.clanki.objects.FlashcardList; +import com.clanki.ui.Ui; + +/** + * The type of command that indicates user have keyed in an invalid input. + */ +public class UnknownCommand extends Command { + @Override + public void execute(FlashcardList flashcardList, Ui display) { + display.printInvalidInput(); + } +} diff --git a/src/main/java/com/clanki/commands/UpdateCommand.java b/src/main/java/com/clanki/commands/UpdateCommand.java new file mode 100644 index 0000000000..19e2a62866 --- /dev/null +++ b/src/main/java/com/clanki/commands/UpdateCommand.java @@ -0,0 +1,90 @@ +package com.clanki.commands; + +import com.clanki.exceptions.UpdatedContentIsEmptyException; +import com.clanki.objects.Flashcard; +import com.clanki.objects.FlashcardList; +import com.clanki.parser.ParsedInput; +import com.clanki.ui.Ui; + +import java.util.ArrayList; + +//@@author javienneyeo +/** + * The UpdateCommand class represents a command that updates an existing + * flashcard in the flashcard list. The command allows users to change the + * question, answer or date of the flashcard. + * + */ +public class UpdateCommand extends Command { + private static final String QUESTION_OPTION_IDENTIFIER = "q"; + private static final String ANSWER_OPTION_IDENTIFIER = "a"; + + String query; + ArrayList matchingFlashcards; + + public UpdateCommand(String query) { + this.matchingFlashcards = new ArrayList<>(); + this.query = query; + } + + /** + * Prompts the user for input and updates the specified flashcard based on the + * user's input. + * + * @param display the Ui object for displaying messages to the user + */ + private void runUpdateFlashcard(Ui display) { + boolean isDone = false; + while (!isDone) { + try { + ParsedInput input = new ParsedInput(display.getUserCommand()); + int index = Integer.parseInt(input.getCommand()); + if (index < 1 || index > matchingFlashcards.size()) { + throw new Exception(); + } + + Flashcard currentFlashcard = matchingFlashcards.get(index - 1); + + String questionText = input.getOptionByName(QUESTION_OPTION_IDENTIFIER); + String answerText = input.getOptionByName(ANSWER_OPTION_IDENTIFIER); + + if (questionText == null && answerText == null) { + throw new UpdatedContentIsEmptyException(); + } + + if (questionText != null) { + currentFlashcard.setQuestion(questionText); + } + + if (answerText != null) { + currentFlashcard.setAnswer(answerText); + } + + display.printSuccessfulUpdateMessage(currentFlashcard); + isDone = true; + } catch (UpdatedContentIsEmptyException e) { + System.out.println("Please enter the changes to be modified."); + } catch (Exception e) { + System.out.println("Please enter a valid index value."); + } + } + } + + @Override + public void execute(FlashcardList flashcardList, Ui display) { + matchingFlashcards = flashcardList.queryFlashcards(query); + + if (matchingFlashcards.size() == 0) { + System.out.printf("No flashcards matching \"%s\" was found. Please try again.", + query); + return; + } + + System.out.printf("Found %d card(s) with query \"%s\":\n", matchingFlashcards.size(), + query); + display.printFlashCards(matchingFlashcards); + System.out.println("Which one do you want to update?"); + + runUpdateFlashcard(display); + } +} diff --git a/src/main/java/com/clanki/commands/VoidCommand.java b/src/main/java/com/clanki/commands/VoidCommand.java new file mode 100644 index 0000000000..c1c5c4294a --- /dev/null +++ b/src/main/java/com/clanki/commands/VoidCommand.java @@ -0,0 +1,15 @@ +package com.clanki.commands; + +import com.clanki.objects.FlashcardList; +import com.clanki.ui.Ui; + +/** + * The type of command that indicate an invalid input, with the display of why + * the input is invalid already handled elsewhere. + */ +public class VoidCommand extends Command { + @Override + public void execute(FlashcardList flashcardList, Ui display) { + return; + } +} diff --git a/src/main/java/com/clanki/exceptions/EmptyFlashcardAnswerException.java b/src/main/java/com/clanki/exceptions/EmptyFlashcardAnswerException.java new file mode 100644 index 0000000000..31b538623a --- /dev/null +++ b/src/main/java/com/clanki/exceptions/EmptyFlashcardAnswerException.java @@ -0,0 +1,4 @@ +package com.clanki.exceptions; + +public class EmptyFlashcardAnswerException extends Exception { +} diff --git a/src/main/java/com/clanki/exceptions/EmptyFlashcardQuestionException.java b/src/main/java/com/clanki/exceptions/EmptyFlashcardQuestionException.java new file mode 100644 index 0000000000..828a9c517e --- /dev/null +++ b/src/main/java/com/clanki/exceptions/EmptyFlashcardQuestionException.java @@ -0,0 +1,4 @@ +package com.clanki.exceptions; + +public class EmptyFlashcardQuestionException extends Exception { +} diff --git a/src/main/java/com/clanki/exceptions/InputIsEmptyException.java b/src/main/java/com/clanki/exceptions/InputIsEmptyException.java new file mode 100644 index 0000000000..bd0ff94790 --- /dev/null +++ b/src/main/java/com/clanki/exceptions/InputIsEmptyException.java @@ -0,0 +1,4 @@ +package com.clanki.exceptions; + +public class InputIsEmptyException extends Exception { +} diff --git a/src/main/java/com/clanki/exceptions/InvalidAddFlashcardInputException.java b/src/main/java/com/clanki/exceptions/InvalidAddFlashcardInputException.java new file mode 100644 index 0000000000..352ab97fed --- /dev/null +++ b/src/main/java/com/clanki/exceptions/InvalidAddFlashcardInputException.java @@ -0,0 +1,4 @@ +package com.clanki.exceptions; + +public class InvalidAddFlashcardInputException extends Exception { +} diff --git a/src/main/java/com/clanki/exceptions/InvalidStorageFilePathException.java b/src/main/java/com/clanki/exceptions/InvalidStorageFilePathException.java new file mode 100644 index 0000000000..4cff4e37ab --- /dev/null +++ b/src/main/java/com/clanki/exceptions/InvalidStorageFilePathException.java @@ -0,0 +1,12 @@ +package com.clanki.exceptions; + +public class InvalidStorageFilePathException extends Exception { + /** + * Signals that the given file path does not fulfill the storage filepath + * constraints. Copied from: + * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/storage/StorageFile.java + */ + public InvalidStorageFilePathException(String message) { + super(message); + } +} diff --git a/src/main/java/com/clanki/exceptions/NoQueryInInputException.java b/src/main/java/com/clanki/exceptions/NoQueryInInputException.java new file mode 100644 index 0000000000..72f3981da4 --- /dev/null +++ b/src/main/java/com/clanki/exceptions/NoQueryInInputException.java @@ -0,0 +1,4 @@ +package com.clanki.exceptions; + +public class NoQueryInInputException extends Exception { +} diff --git a/src/main/java/com/clanki/exceptions/StorageOperationException.java b/src/main/java/com/clanki/exceptions/StorageOperationException.java new file mode 100644 index 0000000000..90482ea22d --- /dev/null +++ b/src/main/java/com/clanki/exceptions/StorageOperationException.java @@ -0,0 +1,13 @@ + +package com.clanki.exceptions; + +/** + * Signals that some error has occured while trying to convert and read/write + * data between the application and the storage file. Copied from: + * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/storage/StorageFile.java + */ +public class StorageOperationException extends Exception { + public StorageOperationException(String message) { + super(message); + } +} diff --git a/src/main/java/com/clanki/exceptions/UpdatedContentIsEmptyException.java b/src/main/java/com/clanki/exceptions/UpdatedContentIsEmptyException.java new file mode 100644 index 0000000000..1b40a02fda --- /dev/null +++ b/src/main/java/com/clanki/exceptions/UpdatedContentIsEmptyException.java @@ -0,0 +1,4 @@ +package com.clanki.exceptions; + +public class UpdatedContentIsEmptyException extends Exception { +} diff --git a/src/main/java/com/clanki/objects/Flashcard.java b/src/main/java/com/clanki/objects/Flashcard.java new file mode 100644 index 0000000000..7c1ec4ab2f --- /dev/null +++ b/src/main/java/com/clanki/objects/Flashcard.java @@ -0,0 +1,92 @@ +package com.clanki.objects; + +import java.time.LocalDate; + +public class Flashcard { + private static final double SPACED_REPETITION_FACTOR = 1.5; + private String questionText; + private String answerText; + private LocalDate dueDate; + private int currentPeriodInDays; + + public Flashcard(String questionText, String answerText) { + this.questionText = questionText; + this.answerText = answerText; + this.dueDate = LocalDate.now(); + this.currentPeriodInDays = 0; + } + + public Flashcard(String questionText, String answerText, LocalDate dueDate, + int currentPeriodInDays) { + this.questionText = questionText; + this.answerText = answerText; + this.dueDate = dueDate; + this.currentPeriodInDays = currentPeriodInDays; + } + + public String getQuestion() { + return questionText; + } + + public void setQuestion(String questionText) { + this.questionText = questionText; + } + + public String getAnswer() { + return answerText; + } + + public void setAnswer(String answerText) { + this.answerText = answerText; + } + + public LocalDate getDueDate() { + return dueDate; + } + + public void setDueDate(LocalDate dueDate) { + this.dueDate = dueDate; + } + + public int getCurrentPeriodInDays() { + return currentPeriodInDays; + } + + public boolean isDueToday() { + return this.dueDate.isEqual(LocalDate.now()); + } + + /** + * This function checks if the flashcard is due or overdue Sometimes the user + * may not clear all flashcards on time, so we may have cards overdue + * + * @return true if the card is due or overdue + */ + public boolean isDueBeforeToday() { + return !LocalDate.now().isBefore(this.getDueDate()); + } + + /** + * Reset dueDate and currentPeriodInDays as if the + * card was just created. + */ + public void updateDueDateAfterIncorrectAnswer() { + this.dueDate = LocalDate.now(); + this.currentPeriodInDays = 0; + } + + /** + * Update dueDate and currentPeriodInDays according to + * the spaced repetition algorithm. Which is very simple, to be honest, but it + * does the job. + */ + public void updateDueDateAfterCorrectAnswer() { + if (this.currentPeriodInDays == 0) { + this.currentPeriodInDays = 1; + } else { + double newPeriod = this.currentPeriodInDays * SPACED_REPETITION_FACTOR; + this.currentPeriodInDays = (int) Math.ceil(newPeriod); + } + this.dueDate = this.dueDate.plusDays(this.currentPeriodInDays); + } +} diff --git a/src/main/java/com/clanki/objects/FlashcardList.java b/src/main/java/com/clanki/objects/FlashcardList.java new file mode 100644 index 0000000000..8c2cf28530 --- /dev/null +++ b/src/main/java/com/clanki/objects/FlashcardList.java @@ -0,0 +1,56 @@ +package com.clanki.objects; + +import java.util.ArrayList; + +public class FlashcardList { + private final ArrayList flashcards; + + public FlashcardList() { + this.flashcards = new ArrayList<>(); + } + + public FlashcardList(ArrayList flashcards) { + this.flashcards = flashcards; + } + + /** + * Adds a new flashcard into the list. + * + * @param questionText Question text of the new flashcard. + * @param answerText Answer text of the new flashcard. + */ + public void addNewFlashcard(String questionText, String answerText) { + Flashcard newFlashcard = new Flashcard(questionText, answerText); + flashcards.add(newFlashcard); + } + + public ArrayList getFlashCards() { + return flashcards; + } + + //@@author javienneyeo + /** + * Query for flashcards in the current deck that matches the query inside + * questions and answers (case-insensitive). + */ + public ArrayList queryFlashcards(String query) { + ArrayList matchingFlashcards = new ArrayList<>(); + String queryLowerCase = query.toLowerCase(); + for (int i = 0; i < flashcards.size(); i++) { + Flashcard currentFlashcard = flashcards.get(i); + if (currentFlashcard.getQuestion().toLowerCase().contains(queryLowerCase) + || currentFlashcard.getAnswer().toLowerCase().contains(queryLowerCase)) { + matchingFlashcards.add(currentFlashcard); + } + } + return matchingFlashcards; + } + + public void deleteFlashcard(int index) { + flashcards.remove(index); + } + + public void deleteAllFlashcards() { + flashcards.clear(); + } +} diff --git a/src/main/java/com/clanki/objects/FlashcardQueue.java b/src/main/java/com/clanki/objects/FlashcardQueue.java new file mode 100644 index 0000000000..f0dc87f9a1 --- /dev/null +++ b/src/main/java/com/clanki/objects/FlashcardQueue.java @@ -0,0 +1,34 @@ +package com.clanki.objects; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Queue; + +public class FlashcardQueue { + private final Queue flashcards; + + public FlashcardQueue(ArrayList flashcards) { + this.flashcards = new LinkedList<>(); + for (Flashcard flashcard : flashcards) { + if (flashcard.isDueBeforeToday()) { + this.flashcards.add(flashcard); + } + } + } + + public void pushFlashcard(Flashcard flashcard) { + flashcards.add(flashcard); + } + + public Flashcard popFlashcard() { + return flashcards.poll(); + } + + public boolean isEmpty() { + return flashcards.isEmpty(); + } + + public int size() { + return flashcards.size(); + } +} diff --git a/src/main/java/com/clanki/parser/ParsedInput.java b/src/main/java/com/clanki/parser/ParsedInput.java new file mode 100644 index 0000000000..5a8d13a97c --- /dev/null +++ b/src/main/java/com/clanki/parser/ParsedInput.java @@ -0,0 +1,80 @@ +package com.clanki.parser; + +class Option { + private static final String BODY_SEPARATOR = " "; + + private String name; + private String value; + + public Option(String optionString) { + String[] parts = optionString.split(BODY_SEPARATOR, 2); + name = parts[0].trim(); + value = parts.length > 1 ? parts[1].trim() : ""; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } +} + +public class ParsedInput { + private static final String OPTION_INDICATOR = "/"; + private static final String OPTION_SEPARATOR = " " + OPTION_INDICATOR; + + private String command; + private String body; + private Option[] options; + + public ParsedInput(String input) { + // Assuming input = "command blah blah /opt1 hello /opt2 world blah bleh" + + // After trimming: + // parts = ["command blah blah", "opt1 hello", "opt2 world blah bleh"] + String[] parts = input.split(OPTION_SEPARATOR); + options = new Option[parts.length - 1]; + + for (int i = 0; i < parts.length; i++) { + Option option = new Option(parts[i].trim()); + if (i == 0) { + command = option.getName(); + body = option.getValue(); + } else { + options[i - 1] = option; + } + } + } + + public String getCommand() { + assert command != null; + return command; + } + + public String getBody() { + return body; + } + + /** + * Returns the value of the option with the given name. + * + * Example: "command blah blah /opt1 hello /opt2 world blah bleh" then + * getOptionByName("opt2") returns "world blah bleh" and getOptionByName("foo") + * returns null. + * + * @param name The name of the option. + * @return The (trimmed) value of the option, or null if the option does not + * exist. Be careful that the option can be an empty string. If an + * option is specified multiple times, the last one will be returned. + */ + public String getOptionByName(String name) { + for (int i = options.length - 1; i >= 0; i--) { + if (options[i].getName().equals(name)) { + return options[i].getValue(); + } + } + return null; + } +} diff --git a/src/main/java/com/clanki/parser/Parser.java b/src/main/java/com/clanki/parser/Parser.java new file mode 100644 index 0000000000..26b2198287 --- /dev/null +++ b/src/main/java/com/clanki/parser/Parser.java @@ -0,0 +1,142 @@ +package com.clanki.parser; + +import com.clanki.commands.AddCommand; +import com.clanki.commands.ByeCommand; +import com.clanki.commands.ClearCommand; +import com.clanki.commands.Command; +import com.clanki.commands.DeleteCommand; +import com.clanki.commands.HelpCommand; +import com.clanki.commands.ListCommand; +import com.clanki.commands.ReviewCommand; +import com.clanki.commands.UnknownCommand; +import com.clanki.commands.UpdateCommand; +import com.clanki.commands.VoidCommand; +import com.clanki.exceptions.EmptyFlashcardAnswerException; +import com.clanki.exceptions.EmptyFlashcardQuestionException; +import com.clanki.exceptions.InputIsEmptyException; +import com.clanki.exceptions.InvalidAddFlashcardInputException; +import com.clanki.exceptions.NoQueryInInputException; + +public class Parser { + private static final String QUESTION_OPTION_IDENTIFIER = "q"; + private static final String ANSWER_OPTION_IDENTIFIER = "a"; + + public static Command parseCommand(String userInput) { + try { + return parseCommandStrict(userInput); + } catch (InvalidAddFlashcardInputException e) { + System.out.println( + "The input is in an incorrect format, please follow the format in user guide"); + } catch (EmptyFlashcardQuestionException e) { + System.out.println("The question of this card is empty, please enter one."); + } catch (EmptyFlashcardAnswerException e) { + System.out.println("The answer for this flashcard is empty, please enter one."); + } catch (NoQueryInInputException e) { + System.out.println("Please enter a query to be searched in the list of flashcards."); + } catch (InputIsEmptyException e) { + System.out.println("Please enter the command in the format shown in the user guide"); + } + return new VoidCommand(); + } + + public static Command parseCommandStrict(String userInput) + throws InvalidAddFlashcardInputException, EmptyFlashcardQuestionException, + EmptyFlashcardAnswerException, NoQueryInInputException, InputIsEmptyException { + ParsedInput parsedInput = new ParsedInput(userInput); + String command = parsedInput.getCommand(); + assert !command.isEmpty() : "The command string must not be empty"; + switch (command) { + case "add": + return getAddCommand(parsedInput); + case "review": + return getReviewCommand(parsedInput); + case "update": + return getUpdateCommand(parsedInput); + case "delete": + return getDeleteCommand(parsedInput); + case "bye": + return getByeCommand(parsedInput); + case "help": + return getHelpCommand(parsedInput); + case "list": + return getListCommand(parsedInput); + case "clear": + return getClearCommand(parsedInput); + default: + return getUnknownCommand(parsedInput); + } + } + + /** + * Constructs an AddCommand from the input of the user, if the input is of an + * incorrect format, a respective exception will be thrown. + * + * @param parsedInput The input collected by Ui from the user, after being + * parsed with the ParsedInput class. + * @return An AddCommand with the question and answer text extracted from user + * input + * @throws InvalidAddFlashcardInputException If the start indicators cannot be + * found. + * @throws EmptyFlashcardQuestionException If the string is empty after + * QUESTION_OPTION_IDENTIFIER. + * @throws EmptyFlashcardAnswerException If the string is empty after + * ANSWER_OPTION_IDENTIFIER. + */ + public static AddCommand getAddCommand(ParsedInput parsedInput) + throws InvalidAddFlashcardInputException, EmptyFlashcardQuestionException, + EmptyFlashcardAnswerException { + String questionText = parsedInput.getOptionByName(QUESTION_OPTION_IDENTIFIER); + String answerText = parsedInput.getOptionByName(ANSWER_OPTION_IDENTIFIER); + if (questionText == null || answerText == null) { + throw new InvalidAddFlashcardInputException(); + } + if (questionText.isEmpty()) { + throw new EmptyFlashcardQuestionException(); + } + if (answerText.isEmpty()) { + throw new EmptyFlashcardAnswerException(); + } + return new AddCommand(questionText, answerText); + } + + public static ReviewCommand getReviewCommand(ParsedInput parsedInput) { + return new ReviewCommand(); + } + + public static UpdateCommand getUpdateCommand(ParsedInput parsedInput) + throws NoQueryInInputException { + String query = parsedInput.getBody(); + if (query.isEmpty()) { + throw new NoQueryInInputException(); + } + return new UpdateCommand(query); + } + + public static DeleteCommand getDeleteCommand(ParsedInput parsedInput) { + return new DeleteCommand(parsedInput.getBody()); + } + + public static ListCommand getListCommand(ParsedInput parsedInput) throws InputIsEmptyException { + String date = parsedInput.getBody(); + if (date.isEmpty()) { + throw new InputIsEmptyException(); + } + return new ListCommand(parsedInput.getBody()); + } + + public static ByeCommand getByeCommand(ParsedInput parsedInput) { + return new ByeCommand(); + } + + public static HelpCommand getHelpCommand(ParsedInput parsedInput) { + return new HelpCommand(); + } + + public static ClearCommand getClearCommand(ParsedInput parsedInput) { + return new ClearCommand(); + } + + public static UnknownCommand getUnknownCommand(ParsedInput parsedInput) { + return new UnknownCommand(); + } +} diff --git a/src/main/java/com/clanki/storage/FlashcardListDecoder.java b/src/main/java/com/clanki/storage/FlashcardListDecoder.java new file mode 100644 index 0000000000..d155d0c88d --- /dev/null +++ b/src/main/java/com/clanki/storage/FlashcardListDecoder.java @@ -0,0 +1,63 @@ +package com.clanki.storage; + +import com.clanki.exceptions.StorageOperationException; +import com.clanki.objects.Flashcard; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This class modifies the list of flashcards from stored text into an ArrayList + * in FlashcardList. Many portions of the code in this class are modified from + * the link below: (addressbook-level2) + * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/storage/AddressBookDecoder.java + */ +public class FlashcardListDecoder { + // '/' forward slashes are reserved for delimiter prefixes + public static final Pattern FLASHCARD_ARGS_FORMAT = Pattern.compile("q/(?[^/]+)" + + " a/(?[^/]+)" + " d/(?[^/]+)" + " p/(?[^/]+)"); + public static final String GROUP_QUESTION = "question"; + public static final String GROUP_ANSWER = "answer"; + public static final String GROUP_DUE_DATE = "dueDate"; + public static final String GROUP_CURRENT_PERIOD = "currentPeriod"; + + /** + * Decodes {@code encodedFlashcardList} into an {@code ArrayList} + * containing the decoded flashcards. + * + * @throws StorageOperationException if the {@code encodedFlashcardList} is in + * an invalid format. + */ + public static ArrayList decodeFlashcardList(List encodedFlashcardList) { + final ArrayList decodedFlashcard = new ArrayList<>(); + for (String encodedFlashcard : encodedFlashcardList) { + try { + decodedFlashcard.add(decodeFlashcardFromString(encodedFlashcard)); + } catch (StorageOperationException e) { + System.out.println("A flashcard is formatted incorrectly, they will be deleted."); + } + } + return decodedFlashcard; + } + + /** + * Decodes {@code encodedFlashcard} into a {@code Flashcard}. + * + * @throws StorageOperationException if {@code encodedFlashcard} is in an + * invalid format. + */ + private static Flashcard decodeFlashcardFromString(String encodedFlashcard) + throws StorageOperationException { + final Matcher matcher = FLASHCARD_ARGS_FORMAT.matcher(encodedFlashcard); + if (!matcher.matches()) { + throw new StorageOperationException( + "Encoded flashcard in invalid format. Unable to decode."); + } + return new Flashcard(matcher.group(GROUP_QUESTION), matcher.group(GROUP_ANSWER), + LocalDate.parse(matcher.group(GROUP_DUE_DATE)), + Integer.parseInt(matcher.group(GROUP_CURRENT_PERIOD))); + } +} diff --git a/src/main/java/com/clanki/storage/FlashcardListEncoder.java b/src/main/java/com/clanki/storage/FlashcardListEncoder.java new file mode 100644 index 0000000000..4fa28d3e2e --- /dev/null +++ b/src/main/java/com/clanki/storage/FlashcardListEncoder.java @@ -0,0 +1,46 @@ +package com.clanki.storage; + +import com.clanki.objects.Flashcard; +import com.clanki.objects.FlashcardList; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class modifies the list of flashcards from FlashcardList into a storable + * format. Many portions of the code in this class are modified from the link + * below: (addressbook-level2) + * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/storage/AddressBookEncoder.java + */ +public class FlashcardListEncoder { + public static final String QUESTION_APPEND = "q/"; + public static final String ANSWER_APPEND = " a/"; + public static final String DUE_DATE_APPEND = " d/"; + public static final String PERIOD_APPEND = " p/"; + + /** + * Encodes all the {@code Flashcards} in the {@code toSave} into a list of + * decodable and readable string presentation for storage. + */ + public static List encodeFlashcardList(FlashcardList toSave) { + final List encodedFlashcards = new ArrayList<>(); + toSave.getFlashCards() + .forEach(flashcard -> encodedFlashcards.add(encodeFlashcardToString(flashcard))); + return encodedFlashcards; + } + + /** + * Encodes the {@code flashcard} into a decodable and readable string + * representation. + */ + private static String encodeFlashcardToString(Flashcard flashcard) { + final StringBuilder encodedFlashcardBuilder = new StringBuilder(); + + encodedFlashcardBuilder.append(QUESTION_APPEND).append(flashcard.getQuestion()); + encodedFlashcardBuilder.append(ANSWER_APPEND).append(flashcard.getAnswer()); + encodedFlashcardBuilder.append(DUE_DATE_APPEND).append(flashcard.getDueDate().toString()); + encodedFlashcardBuilder.append(PERIOD_APPEND).append(flashcard.getCurrentPeriodInDays()); + + return encodedFlashcardBuilder.toString(); + } +} diff --git a/src/main/java/com/clanki/storage/StorageFile.java b/src/main/java/com/clanki/storage/StorageFile.java new file mode 100644 index 0000000000..033826c1fb --- /dev/null +++ b/src/main/java/com/clanki/storage/StorageFile.java @@ -0,0 +1,92 @@ +package com.clanki.storage; + +import com.clanki.exceptions.InvalidStorageFilePathException; +import com.clanki.exceptions.StorageOperationException; +import com.clanki.objects.Flashcard; +import com.clanki.objects.FlashcardList; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +/** + * This class manages the storing of FlashcardList and the recovering of stored + * data each time the application first start up. Many portions of the code in + * this class are modified from the link below: (addressbook-level2) + * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/storage/StorageFile.java + */ +public class StorageFile { + /** + * Default file path used if the user doesn't provide the file name. + */ + public static final String DEFAULT_STORAGE_FILEPATH = "flashcardList.txt"; + public static final String FILE_END_STRING = ".txt"; + + public final Path path; + + /** + * @throws InvalidStorageFilePathException if the default path is invalid + */ + public StorageFile() throws InvalidStorageFilePathException { + this(DEFAULT_STORAGE_FILEPATH); + } + + /** + * @throws InvalidStorageFilePathException if the given file path is invalid + */ + public StorageFile(String filePath) throws InvalidStorageFilePathException { + path = Paths.get(filePath); + if (!isValidPath(path)) { + throw new InvalidStorageFilePathException("Storage file should end with '.txt'"); + } + } + + /** + * Returns true if the given path is acceptable as a storage file. The file path + * is considered acceptable if it ends with '.txt' + */ + private static boolean isValidPath(Path filePath) { + return filePath.toString().endsWith(FILE_END_STRING); + } + + /** + * Saves the {@code addressBook} data to the storage file. + * + * @throws StorageOperationException if there were errors converting and/or + * storing data to file. + */ + public void save(FlashcardList flashcardList) throws StorageOperationException { + try { + List encodedFlashcardList = FlashcardListEncoder + .encodeFlashcardList(flashcardList); + Files.write(path, encodedFlashcardList); + } catch (IOException ioe) { + throw new StorageOperationException("Error writing to file: " + path); + } + } + + /** + * Loads the {@code AddressBook} data from this storage file, and then returns + * it. Returns an empty {@code AddressBook} if the file does not exist, or is + * not a regular file. + * + * @throws StorageOperationException if there were errors reading and/or + * converting data from file. + */ + public ArrayList load() throws StorageOperationException { + if (!Files.exists(path) || !Files.isRegularFile(path)) { + return new ArrayList<>(); + } + try { + return FlashcardListDecoder.decodeFlashcardList(Files.readAllLines(path)); + } catch (FileNotFoundException fnfe) { + throw new AssertionError("A non-existent file scenario is already handled earlier."); + } catch (IOException ioe) { + throw new StorageOperationException("Error writing to file: " + path); + } + } +} diff --git a/src/main/java/com/clanki/ui/Ui.java b/src/main/java/com/clanki/ui/Ui.java new file mode 100644 index 0000000000..1f417c846f --- /dev/null +++ b/src/main/java/com/clanki/ui/Ui.java @@ -0,0 +1,139 @@ +package com.clanki.ui; + +import com.clanki.objects.Flashcard; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Scanner; + +/** + * Parts of the code below are copied and adapted from TextUi.java of + * addressbook-level2, link of original code: + * https://github.com/se-edu/addressbook-level2/blob/master/src/seedu/addressbook/ui/TextUi.java. + */ +public class Ui { + private final Scanner in; + + public Ui() { + this(System.in); + } + + public Ui(InputStream in) { + this.in = new Scanner(in); + } + + public String getUserCommand() { + return in.nextLine(); + } + + /** + * Display messages to inform user that a flashcard have been successfully + * added. + * + * @param questionText The question of the new flashcard. + * @param answerText The answer of the new flashcard. + */ + public void printSuccessfulAddMessage(String questionText, String answerText) { + System.out.println("You have added the following card:"); + System.out.println("Q: " + questionText); + System.out.println("A: " + answerText); + } + + public void printByeMessage() { + System.out.println("Bye. Don't forget to come back later to study!"); + } + + public void printWelcomeMessage() { + System.out.println("Welcome to Clanki! Time to start studying!"); + } + + /** + * Display message to inform user that their input is of a wrong format. + */ + public void printInvalidInput() { + System.out.println("The input is in an incorrect format.\n" + + "You can view our user guide or type help to see the correct formats for commands.\n"); + } + + /** + * Prints out a message to indicate a successful deletion of a flashcard + * + * @param index the index of the flashcard at the point of deletion + */ + public void printSuccessfulDelete(int index) { + System.out.println("Got it. Deleted the flashcard at index " + index); + } + + /** + * reused from Kong Dehao ip for general printing to console + * + * @param message multiple strings to be shown to user + */ + public void printlnSeveralStrings(String... message) { + for (String m : message) { + System.out.println(m); + } + } + + /** + * Display all the current valid commands the user can key in. + */ + public void printHelpMessage() { + System.out.println("The following are the commands you can use:\n" + + "add Adds a flashcard to the current list of flashcards.\n" + + " Parameters: add /q QUESTION /a ANSWER\n" + + " Example: add /q What is the worst fruit? /a Durian\n" + + "update Changes the content of flashcard's question or answer.\n" + + " Parameters: update QUERY\n" + " Example: update fruit\n" + + " Which flashcard do you want to update? 1 /q What is the best fruit?\n" + + "delete Removes a flashcard with specified string.\n" + + " Parameters: delete QUERY\n" + " Example: delete fruit\n" + + "review Go through all flashcards that are due today.\n" + + "list Lists out the questions and answers in the list of flashcards.\n" + + " Parameters: list all (lists all flashcards)\n" + + " Parameters: list DUE_DATE (list all flashcards with that specified due date)\n" + + " Example: list 2023-05-04\n" + + "clear Deletes all the flashcards in the list.\n" + + "bye Exit the program."); + } + + public void printSeparationLine() { + System.out.println("=========================================================="); + } + + /** + * Prints the question and answer of a single flashcard to the console. + * + * @Param flashcard the flashcard to be print + */ + public void printFlashCard(Flashcard flashcard) { + System.out.println("Q: " + flashcard.getQuestion()); + System.out.println("A: " + flashcard.getAnswer()); + } + + /** + * Prints the questions and answers of a list of flashcards to the console. + * + * @param flashcards the list of flashcards to be printed + */ + public void printFlashCards(ArrayList flashcards) { + for (int i = 0; i < flashcards.size(); i++) { + System.out.println("[" + (i + 1) + "]"); + printFlashCard(flashcards.get(i)); + } + } + + public void printSuccessfulUpdateMessage(Flashcard updatedFlashcard) { + System.out.println("Understood. The card has been updated to"); + printFlashCard(updatedFlashcard); + } + + //@@author javienneyeo + /** + * Display that the contents of list have been successfully cleared. + */ + public void printClearMessage() { + System.out.println("All flashcards have been deleted."); + System.out.println("Your list of flashcards is now empty."); + } +} diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java deleted file mode 100644 index 5c74e68d59..0000000000 --- a/src/main/java/seedu/duke/Duke.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.duke; - -import java.util.Scanner; - -public class Duke { - /** - * Main entry-point for the java.duke.Duke application. - */ - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - System.out.println("What is your name?"); - - Scanner in = new Scanner(System.in); - System.out.println("Hello " + in.nextLine()); - } -} diff --git a/src/test/java/com/clanki/objects/FlashcardTest.java b/src/test/java/com/clanki/objects/FlashcardTest.java new file mode 100644 index 0000000000..d8049e9422 --- /dev/null +++ b/src/test/java/com/clanki/objects/FlashcardTest.java @@ -0,0 +1,51 @@ + +package com.clanki.objects; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.LocalDate; + +class FlashcardTest { + @Test + public void constructNewFlashcard_correctlyFormattedInputs_success() { + Flashcard testCard = new Flashcard("QUESTION", "ANSWER"); + assertEquals("QUESTION", testCard.getQuestion()); + assertEquals("ANSWER", testCard.getAnswer()); + assertEquals(LocalDate.now(), testCard.getDueDate()); + assertEquals(0, testCard.getCurrentPeriodInDays()); + assertEquals(true, testCard.isDueToday()); + } + + @Test + public void updateFlashcard_incorrectAnswer_shouldResetDatesToToday() { + Flashcard testCard = new Flashcard("QUESTION", "ANSWER", LocalDate.now(), 1); + testCard.updateDueDateAfterIncorrectAnswer(); + assertEquals(LocalDate.now(), testCard.getDueDate()); + assertEquals(0, testCard.getCurrentPeriodInDays()); + } + + @Test + public void updateFlashcard_correctAnswer_shouldUpdateDates() { + Flashcard testCard = new Flashcard("q1", "a1"); + testCard.updateDueDateAfterCorrectAnswer(); + assertEquals(LocalDate.now().plusDays(1), testCard.getDueDate()); + assertEquals(1, testCard.getCurrentPeriodInDays()); + + testCard = new Flashcard("q2", "a2", LocalDate.now(), 1); + testCard.updateDueDateAfterCorrectAnswer(); + assertEquals(LocalDate.now().plusDays(2), testCard.getDueDate()); + assertEquals(2, testCard.getCurrentPeriodInDays()); + + testCard = new Flashcard("q3", "a3", LocalDate.now(), 2); + testCard.updateDueDateAfterCorrectAnswer(); + assertEquals(LocalDate.now().plusDays(3), testCard.getDueDate()); + assertEquals(3, testCard.getCurrentPeriodInDays()); + + testCard = new Flashcard("q4", "a4", LocalDate.now(), 45); + testCard.updateDueDateAfterCorrectAnswer(); + assertEquals(LocalDate.now().plusDays(68), testCard.getDueDate()); + assertEquals(68, testCard.getCurrentPeriodInDays()); + } +} diff --git a/src/test/java/com/clanki/parser/ParsedInputTest.java b/src/test/java/com/clanki/parser/ParsedInputTest.java new file mode 100644 index 0000000000..088de8c70f --- /dev/null +++ b/src/test/java/com/clanki/parser/ParsedInputTest.java @@ -0,0 +1,84 @@ +package com.clanki.parser; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ParsedInputTest { + @Test + public void getCommand_emptyInput_emptyString() { + ParsedInput parsedInput = new ParsedInput(""); + assertEquals("", parsedInput.getCommand()); + } + + @Test + public void getCommand_oneWordInput_oneWord() { + ParsedInput parsedInput = new ParsedInput("command"); + assertEquals("command", parsedInput.getCommand()); + } + + @Test + public void getCommand_multipleWordsInput_firstWord() { + ParsedInput parsedInput = new ParsedInput("command blah blah"); + assertEquals("command", parsedInput.getCommand()); + } + + @Test + public void getCommand_withSlash_hasTheSlash() { + ParsedInput parsedInput = new ParsedInput("command/blah blah"); + assertEquals("command/blah", parsedInput.getCommand()); + } + + @Test + public void getBody_emptyInput_emptyString() { + ParsedInput parsedInput = new ParsedInput(""); + assertEquals("", parsedInput.getBody()); + } + + @Test + public void getBody_oneWordInput_emptyString() { + ParsedInput parsedInput = new ParsedInput("command"); + assertEquals("", parsedInput.getBody()); + } + + @Test + public void getBody_multipleWordsInput_allWords() { + ParsedInput parsedInput = new ParsedInput("command blah blah"); + assertEquals("blah blah", parsedInput.getBody()); + } + + @Test + public void getBody_optionsWithoutBody_beNull() { + ParsedInput parsedInput = new ParsedInput("command /blah blah"); + assertEquals("", parsedInput.getBody()); + } + + @Test + public void getBody_optionsWithBody_beCorrect() { + ParsedInput parsedInput = new ParsedInput("command a b /blah blah"); + assertEquals("a b", parsedInput.getBody()); + } + + @Test + public void getBody_optionsWithBodyAndTrailingSpaces_beCorrect() { + ParsedInput parsedInput = new ParsedInput("command a b /blah blah "); + assertEquals("a b", parsedInput.getBody()); + } + + @Test + public void getOptionByName_normalScenarios_beCorrect() { + ParsedInput parsedInput = new ParsedInput( + "command /opt1 blah /opt2 /opt3 bleh "); + assertEquals("blah", parsedInput.getOptionByName("opt1")); + assertEquals("", parsedInput.getOptionByName("opt2")); + assertEquals("bleh", parsedInput.getOptionByName("opt3")); + assertEquals(null, parsedInput.getOptionByName("opt4")); + } + + @Test + public void getOptionByName_sameOptionMultipleTimes_selectLast() { + ParsedInput parsedInput = new ParsedInput( + "command /opt1 blah /opt2 /opt3 bleh /opt1 bleh "); + assertEquals("bleh", parsedInput.getOptionByName("opt1")); + } +} diff --git a/src/test/java/com/clanki/parser/ParserTest.java b/src/test/java/com/clanki/parser/ParserTest.java new file mode 100644 index 0000000000..d47c4fc9e3 --- /dev/null +++ b/src/test/java/com/clanki/parser/ParserTest.java @@ -0,0 +1,95 @@ + +package com.clanki.parser; + +import com.clanki.commands.AddCommand; +import com.clanki.commands.ByeCommand; +import com.clanki.commands.Command; +import com.clanki.commands.DeleteCommand; +import com.clanki.commands.ListCommand; +import com.clanki.commands.ReviewCommand; +import com.clanki.commands.UnknownCommand; +import com.clanki.commands.UpdateCommand; +import com.clanki.exceptions.EmptyFlashcardAnswerException; +import com.clanki.exceptions.EmptyFlashcardQuestionException; +import com.clanki.exceptions.InvalidAddFlashcardInputException; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ParserTest { + @Test + public void parserAddCommand_correctlyFormattedInput_successful() { + Command parsedCommand = Parser.parseCommand("add /q Question /a Answer"); + Command expectedCommand = new AddCommand("Question", "Answer"); + assertEquals(expectedCommand.toString(), parsedCommand.toString()); + Command parsedCommandNonCap = Parser.parseCommand("add /q question /a answer"); + Command expectedCommandCap = new AddCommand("Question", "Answer"); + assertEquals(expectedCommandCap.toString(), parsedCommandNonCap.toString()); + } + + @Test + public void parserAddCommand_answerBeforeQuestion_successful() { + Command parsedCommand = Parser.parseCommand("add /a Answer /q Question"); + Command expectedCommand = new AddCommand("Question", "Answer"); + assertEquals(expectedCommand.toString(), parsedCommand.toString()); + } + + @Test + public void parserAddCommand_incorrectFormattedInput_invalidInputException() { + assertThrows(InvalidAddFlashcardInputException.class, + () -> Parser.parseCommandStrict("add Question /a Answer")); + assertThrows(InvalidAddFlashcardInputException.class, + () -> Parser.parseCommandStrict("add /q Question /w Answer")); + } + + @Test + public void parserAddCommand_partOfInputMissing_emptyInputPartException() { + assertThrows(EmptyFlashcardAnswerException.class, + () -> Parser.parseCommandStrict("add /q Question /a ")); + assertThrows(EmptyFlashcardQuestionException.class, + () -> Parser.parseCommandStrict("add /q /a Answer")); + } + + @Test + public void parserByeCommand_byeCommand_successful() { + Command parsedCommand = Parser.parseCommand("bye"); + assertTrue(parsedCommand instanceof ByeCommand); + parsedCommand = Parser.parseCommand("bye whatever"); + assertTrue(parsedCommand instanceof ByeCommand); + } + + @Test + public void parserReviewCommand_reviewCommand_successful() { + Command parsedCommand = Parser.parseCommand("review"); + assertTrue(parsedCommand instanceof ReviewCommand); + } + + @Test + public void parserUnknownCommand_unknownCommand_successful() { + Command parsedCommand = Parser.parseCommand("unknown"); + assertTrue(parsedCommand instanceof UnknownCommand); + } + + @Test + public void parserUpdateCommand_updateCommand_successful() { + Command parsedCommand = Parser.parseCommand("update Question"); + assertTrue(parsedCommand instanceof UpdateCommand); + } + + @Test + public void parserDeleteCommand_deleteCommand_successful() { + Command parsedCommand = Parser.parseCommand("delete query"); + assertTrue(parsedCommand instanceof DeleteCommand); + } + + @Test + public void parserListCommand_listCommand_successful() { + Command parsedCommand = Parser.parseCommand("list all"); + assertTrue(parsedCommand instanceof ListCommand); + parsedCommand = Parser.parseCommand("list 2023-04-06"); + assertTrue(parsedCommand instanceof ListCommand); + } +} diff --git a/src/test/java/com/clanki/storage/FlashcardListDecoderTest.java b/src/test/java/com/clanki/storage/FlashcardListDecoderTest.java new file mode 100644 index 0000000000..5f642bab4e --- /dev/null +++ b/src/test/java/com/clanki/storage/FlashcardListDecoderTest.java @@ -0,0 +1,66 @@ + +package com.clanki.storage; + +import com.clanki.objects.Flashcard; +import com.clanki.objects.FlashcardList; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import static com.clanki.storage.FlashcardListDecoder.decodeFlashcardList; +import static com.clanki.storage.FlashcardListEncoder.encodeFlashcardList; +import static org.junit.jupiter.api.Assertions.assertEquals; + + +class FlashcardListDecoderTest { + + @Test + void decodeFlashcardList_correctInput_correctOutput() { + List storedString = new ArrayList<>(); + storedString.add("q/q1 a/a1 d/" + LocalDate.now().toString() + " p/0"); + storedString.add("q/q2 a/a2 d/" + LocalDate.now().toString() + " p/0"); + ArrayList decodedFlashcardList = decodeFlashcardList(storedString); + Flashcard testCard1 = decodedFlashcardList.get(0); + assertEquals("q1", testCard1.getQuestion()); + assertEquals("a1", testCard1.getAnswer()); + assertEquals(LocalDate.now(), testCard1.getDueDate()); + assertEquals(0, testCard1.getCurrentPeriodInDays()); + Flashcard testCard2 = decodedFlashcardList.get(1); + assertEquals("q2", testCard2.getQuestion()); + assertEquals("a2", testCard2.getAnswer()); + assertEquals(LocalDate.now(), testCard2.getDueDate()); + assertEquals(0, testCard2.getCurrentPeriodInDays()); + } + + @Test + void decodeAddressBook_encodeInput_decodeOutput() { + FlashcardList flashcardList = new FlashcardList(); + flashcardList.addNewFlashcard("q1", "a1"); + flashcardList.addNewFlashcard("q2", "a2"); + List encodedResult = encodeFlashcardList(flashcardList); + ArrayList decodedFlashcardList = decodeFlashcardList(encodedResult); + Flashcard testCard1 = decodedFlashcardList.get(0); + assertEquals("q1", testCard1.getQuestion()); + assertEquals("a1", testCard1.getAnswer()); + assertEquals(LocalDate.now(), testCard1.getDueDate()); + assertEquals(0, testCard1.getCurrentPeriodInDays()); + Flashcard testCard2 = decodedFlashcardList.get(1); + assertEquals("q2", testCard2.getQuestion()); + assertEquals("a2", testCard2.getAnswer()); + assertEquals(LocalDate.now(), testCard2.getDueDate()); + assertEquals(0, testCard2.getCurrentPeriodInDays()); + } + + @Test + void decodeAddressBook_correctAndInvalidInput_correctOutput() { + List storedString = new ArrayList<>(); + storedString.add("q1 a/a1 d/" + LocalDate.now().toString() + " p/0"); + storedString.add("q/q2 a/a2 d/" + LocalDate.now().toString() + " p/0"); + ArrayList decodedFlashcardList = decodeFlashcardList(storedString); + assertEquals(1, decodedFlashcardList.size()); + assertEquals("q2", decodedFlashcardList.get(0).getQuestion()); + } + +} diff --git a/src/test/java/com/clanki/storage/FlashcardListEncoderTest.java b/src/test/java/com/clanki/storage/FlashcardListEncoderTest.java new file mode 100644 index 0000000000..bbdd5ca138 --- /dev/null +++ b/src/test/java/com/clanki/storage/FlashcardListEncoderTest.java @@ -0,0 +1,35 @@ + +package com.clanki.storage; + +import com.clanki.objects.FlashcardList; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.List; + +import static com.clanki.storage.FlashcardListEncoder.encodeFlashcardList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class FlashcardListEncoderTest { + + @Test + void encodeAddressBook_correctInput_correctOutput() { + FlashcardList flashcardList = new FlashcardList(); + flashcardList.addNewFlashcard("q1", "a1"); + flashcardList.addNewFlashcard("q2", "a2"); + List encodedResult = encodeFlashcardList(flashcardList); + String line1 = "q/q1 a/a1 d/" + LocalDate.now().toString() + " p/0"; + String line2 = "q/q2 a/a2 d/" + LocalDate.now().toString() + " p/0"; + assertEquals(line1, encodedResult.get(0)); + assertEquals(line2, encodedResult.get(1)); + } + + @Test + void encodeAddressBook_emptyInput() { + FlashcardList flashcardList = new FlashcardList(); + List encodedResult = encodeFlashcardList(flashcardList); + assertTrue(encodedResult.isEmpty()); + } + +} diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/duke/DukeTest.java deleted file mode 100644 index 2dda5fd651..0000000000 --- a/src/test/java/seedu/duke/DukeTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package seedu.duke; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -class DukeTest { - @Test - public void sampleTest() { - assertTrue(true); - } -} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..58273c51e7 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +1,40 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| +Welcome to Clanki! Time to start studying! +========================================================== +You have added the following card: +Q: Q1 +A: A1 +========================================================== +You have added the following card: +Q: Q1 +A: A1 +========================================================== +Found 2 card(s) with query "q1": +[1] +Q: Q1 +A: A1 +[2] +Q: Q1 +A: A1 +Which one do you want to update? +Understood. The card has been updated to +Q: q2 +A: A1 +========================================================== +There are 2 cards available for review today. +--- +Q: Q1 (ENTER to view answer)A: A1 +Did you get it right? (y/n) Great, you got it right! +--- +Q: q2 (ENTER to view answer)A: A1 +Did you get it right? (y/n) Please enter 'y' or 'n': (y/n) Please enter 'y' or 'n': (y/n) No worries, we will try again later today. +--- +Q: q2 (ENTER to view answer)A: A1 +Did you get it right? (y/n) Great, you got it right! +--- +Congrats! You have reviewed all the flashcards due today! +========================================================== +The input is in an incorrect format. +You can view our user guide or type help to see the correct formats for commands. -What is your name? -Hello James Gosling +========================================================== +Bye. Don't forget to come back later to study! diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..8d5df9f7a2 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +1,15 @@ -James Gosling \ No newline at end of file +add /q q1 /a a1 +add /q q1 /a a1 +update q1 +2 /q q2 +review + +y + + +blah +n + +y +foo 1 +bye diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 25ac7a2989..53335711a1 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -16,4 +16,4 @@ java -jar %jarloc% < ..\..\text-ui-test\input.txt > ..\..\text-ui-test\ACTUAL.TX cd ..\..\text-ui-test -FC ACTUAL.TXT EXPECTED.TXT >NUL && ECHO Test passed! || Echo Test failed! +FC ACTUAL.TXT EXPECTED.TXT >NUL && ECHO Test passed! || Echo Test failed! \ No newline at end of file diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index 1dcbd12021..b6e8516ff8 100755 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -11,7 +11,6 @@ cd text-ui-test java -jar $(find ../build/libs/ -mindepth 1 -print -quit) < input.txt > ACTUAL.TXT cp EXPECTED.TXT EXPECTED-UNIX.TXT -dos2unix EXPECTED-UNIX.TXT ACTUAL.TXT diff EXPECTED-UNIX.TXT ACTUAL.TXT if [ $? -eq 0 ] then