By: Team T09-2      Since: Aug 2018      Licence: MIT

1. Setting up

1.1. Prerequisites

  1. JDK 9 or later

    JDK 10 on Windows will fail to run tests in headless mode due to a JavaFX bug. Windows developers are highly recommended to use JDK 9.
  2. IntelliJ IDE

    IntelliJ by default has Gradle and JavaFx plugins installed.
    Do not disable them. If you have disabled them, go to File > Settings > Plugins to re-enable them.

1.2. Setting up the project in your computer

  1. Fork this repo, and clone the fork to your computer

  2. Open IntelliJ (if you are not in the welcome screen, click File > Close Project to close the existing project dialog first)

  3. Set up the correct JDK version for Gradle

    1. Click Configure > Project Defaults > Project Structure

    2. Click New…​ and find the directory of the JDK

  4. Click Import Project

  5. Locate the build.gradle file and select it. Click OK

  6. Click Open as Project

  7. Click OK to accept the default settings

  8. Open a console and run the command gradlew processResources (Mac/Linux: ./gradlew processResources). It should finish with the BUILD SUCCESSFUL message.
    This will generate all resources required by the application and tests.

1.3. Verifying the setup

  1. Run the seedu.address.MainApp and try a few commands

  2. Run the tests to ensure they all pass.

1.4. Configurations to do before writing code

1.4.1. Configuring the coding style

This project follows oss-generic coding standards. IntelliJ’s default style is mostly compliant with ours but it uses a different import order from ours. To rectify,

  1. Go to File > Settings…​ (Windows/Linux), or IntelliJ IDEA > Preferences…​ (macOS)

  2. Select Editor > Code Style > Java

  3. Click on the Imports tab to set the order

    • For Class count to use import with '*' and Names count to use static import with '*': Set to 999 to prevent IntelliJ from contracting the import statements

    • For Import Layout: The order is import static all other imports, import java.*, import javax.*, import org.*, import com.*, import all other imports. Add a <blank line> between each import

Optionally, you can follow the UsingCheckstyle.adoc document to configure Intellij to check style-compliance as you write code.

2. Design

2.1. Architecture

Architecture
Figure 1. Architecture Diagram

The Architecture Diagram given above explains the high-level design of the App. Given below is a quick overview of each component.

Main has only one class called MainApp. It is responsible for,

  • At app launch: Initializes the components in the correct sequence, and connects them up with each other.

  • At shut down: Shuts down the components and invokes cleanup method where necessary.

Commons represents a collection of classes used by multiple other components. Two of those classes play important roles at the architecture level.

  • EventsCenter : This class (written using Google’s Event Bus library) is used by components to communicate with other components using events (i.e. a form of Event Driven design)

  • LogsCenter : Used by many classes to write log messages to the App’s log file.

The rest of the App consists of four components.

  • UI: The UI of the App.

  • Logic: The command executor.

  • Model: Holds the data of the App in-memory.

  • Storage: Reads data from, and writes data to, the hard disk.

Each of the four components

  • Defines its API in an interface with the same name as the Component.

  • Exposes its functionality using a {Component Name}Manager class.

The sections below give more details of each component.

2.2. UI component

AnakinUIClassDiagram
Figure 2. Structure of the UI Component

API : Ui.java

The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, DeckEditScreen, DeckReviewScreen, StatusBarFooter etc. The DeckEditScreen is in turn made up of DeckListPanel and CardListPanel and the DeckReviewScreen is made up of DeckReviewCard. All these, including the MainWindow, inherit from the abstract UiPart class.

The UI component uses JavaFx UI framework. The layout of these UI parts are defined in matching .fxml files that are in the src/main/resources/view folder. For example, the layout of the MainWindow is specified in MainWindow.fxml

The UI component

  • Executes user commands using the Logic component.

  • Binds itself to some data in the Model so that the UI can auto-update when data in the Model changes.

  • Responds to events raised from various parts of the App and updates the UI accordingly.

2.3. Logic component

AnakinLogicClassDiagram

Structure of the Logic component

The 'Logic' component:

  • exposes functionality through LogicManager

  • parses user input

  • create commands

  • modifies Model

The LogicManager contains an AnakinModel, an AnakinParser, and a CommandHistory.

When a string input by the user is parsed by AnakinParser, it creates a new AnakinCommand of the appropriate type with the relevant arguments (based on parser tokens). Each AnakinCommand has its own Class. LogicManager will call execute on the command object. If successful, LogicManager will modify Model accordingly. Regardless of success, LogicManager will update CommandHistory with the CommandResult.

2.4. Model component

AnakinModelClassDiagram

Structure of the Model Component

The Model,

  • stores Anakin’s data and manipulates the state of the application.

  • provides a Model interface for Logic component to execute its set of commands.

  • manages the interaction and relationship between different objects (Anakin, Card, Deck, …​)

  • exposes an unmodifiable ObservableList<Deck> and ObservableList<Card> that can be 'observed' e.g. the UI is bound to these lists so that the UI automatically updates when the data in the lists change.

  • does not depend on any of the other three components.

2.5. Storage component

This section details the Storage component, which is the bridge between internal application state and external files.

StorageClassDiagram
Figure 3. Structure of the Storage Component

API : Storage.java

The Storage component,

  • can store UserPref objects in json format and read it back

  • can save Anakin’s data in XML format and read it back

3. Implementation

This section describes some noteworthy details on how certain features are implemented.

3.1. Card-level operations

3.1.1. Current implementation

Card-level operations are supported in Anakin class:

  • Anakin#addCard(Card card) - Add a card to the current deck.

  • Anakin#deleteCard(Card card) - Delete a card from the current deck.

  • Anakin#updateCard( Card target, Card editedCard) - Update the information of a card inside the current deck.

These operations are exposed in the Model interface as: Model#addCard(Card card), Model#deleteCard(Card card), Model#updateCard(Card target, Card editedCard) respectively.

Given below is an example usage scenario and how these operations are executed in Model.

The user executes newcard q/Hello? a/World to add a new card with question "Hello?" and answer "World" to the current deck.

  1. The newcard command calls Model#addCard(Card card), or ModelManager#addCard.

  2. ModelManager, which implements Model interface, will call Anakin#addCard method.

  3. Anakin#addCard will throw DeckNotFoundException if the user is not inside a deck. Otherwise, it will call method UniqueCardList#add(Card toAdd).

  4. UniqueCardList#add will throw DuplicateCardException if the card already exist in the list. Otherwise, it will add the card to its internal ObservableList<Card>.

The following sequence diagram shows how the newcard operation works:

NewCardSequenceDiagram

3.1.2. Design consideration

  • Alternative 1 (current choice): Implement the logic of card-level operations in Anakin class.

    • Pros: Easy to implement and debug as all logic related with executing commands are implemented in Anakin.

    • Cons: Deck class is not informed, or notified when its UniqueCardList is modified. This might result in unexpected behaviors if a card-level command is executed and the person in charge of Deck class assumes that the UniqueCardList is unmodified.

  • Alternative 2: Implement the logic of card-level operations in Deck class.

    • Pros: The responsibility of each class is clear, only a Deck can modify its list of cards.

    • Cons: The logic for executing deck-level and card-level commands are implemented at different places. We must ensure that the implementation of each command is correct.

3.2. Handle invalid commands

3.2.1. Current implementation

  • Deck-level operations (newdeck, editdeck, deldeck, exportdeck, importdeck) are disabled when user is inside a deck.

  • These commands will be blocked in Logic component by checking the state of the application through Model interface.

3.2.2. Design consideration

  • Alternative 1 (current choice): Implement the logic of command-checking in Logic component

    • Pros:

      • Reduce possible bugs caused by having each methods check the state of the application before execution.

      • Model component does not have to handle invalid commands.

      • Exceptions are thrown and handled inside Logic components instead of being passed from Model to Logic.

    • Cons: Increase coupling between Logic and Model component.

  • Alternative 2: Handle all commands logic in Model component

    • Pros: Lower degree of dependency between Logic and Model (aka low coupling), thus make the process of maintenance, integration and testing easier.

    • Cons: Model component has to check for invalid commands according to its state for all commands.

3.3. Deck Review Implementation

3.3.1. Current Implementation

The deck review mechanism is facilitated by Anakin from Model, the MainWindow and DeckReviewScreen from UI and the EventsCenter.

It implements the following operations:

  • Anakin#isReviewingDeck() - Checks if user is in review mode.

  • Anakin#startReview() - Starts a review.

  • Anakin#endReview() - Ends a review.

  • Anakin#getIndexOfCurrentCard() - Retrieves the index of the last known reviewed card.

  • Anakin#setIndexOfCurrentCard() - Updates the index of the last known reviewed card.

These operations are exposed in the Model interface as Model#isReviewingDeck(), Model#startReview(), Model#endReview(), Model#getIndexOfCurrentCard() and Model#setIndexOfCurrentCard respectively.

Also, it is supported by the following commands from Logic:

  • review - Starts the review of a selected deck

  • endreview - End the review

  • nextcard - Moves to the subsequent card in the deck

  • prevcard - Moves to the previous card in the deck

  • flipcard - Flips the current card to display either the question or the answer

Furthermore, it posts the following events to trigger changes in UI:

  • StartReviewRequestEvent(Card card)

  • FlipCardRequestEvent()

  • ReviewNextCardEvent(Card nextCard)

  • ReviewPreviousCardEvent(Card prevCard)

  • EndReviewRequestEvent()

Given below is an example usage scenario and how the deck review mechanism behaves at each step.

Step 1. The user launches the application and he already has a deck of cards that he wants to review. He executes review 1 command to review the first deck on the list.

  1. The review command calls Model#getIntoDeck(Deck deck).

  2. It then calls Model#startReview() which updates the isReviewingDeck boolean in Anakin to true.

  3. It also calls Model#getFilteredCardList() and Model#getIndexOfCurrentCard() to retrieve the Card object of the last reviewed card.

  4. Thereafter, it posts a StartReviewRequestEvent(Card card) with the EventsCenter.

  5. MainWindow, on the UI side, subscribes to this event and switches DeckEditScreen with DeckReviewScreen by reordering the nodes in mainAreaPlaceholder, which is a JavaFX StackPane.

The following sequence diagram shows how the review operation works:

ReviewSequenceDiagram

Step 2. The user sees the question displayed on the first card and comes up with his own answer. To verify his answer, he executes the flipcard command.

  1. The flipcard command posts a FlipCardRequestEvent with EventsCenter.

  2. DeckReviewScreen is notified of the event and switches the card with the question with the other card displaying the answer. This is also achieved by reordering the nodes in a StackPane but this time in reviewAreaPlaceholder. As a result, the user is presented with the answer to the question.

Step 3. The user wants to go to the next card and executes nextcard command.

  1. The nextcard command calls Model#getFilteredCardList() and Model#getIndexOfCurrentCard() and increments the index by one.

  2. If the incremented index is equal to the size of the card list, it reassigns the index a value of 0. This is to ensure that calling nextcard on the last card of the deck will loop back to the first card.

  3. Using the Card object found at the new index, the command posts a ReviewNextCardEvent using EventsCenter.

  4. DeckReviewScreen is subscribed to the event and uses the Card object to create question and answer `DeckReviewCard`s and replaces the currently displayed cards with these two new ones.

Step 4. If the user wants to go back to review a previous card, he executes prevcard command. The explanation of the process is similar to Step 3.

Step 5. When the user is done, he executes endreview to quit review mode.

  1. The endreview command posts a EndReviewRequestEvent using EventsCenter.

  2. MainWindow is subscribed to the event and switches the DeckEditScreen back to the front by reordering the nodes in mainAreaPlaceholder and the user is able to edit his decks again.

The following activity diagram summarizes what happens when a user enters deck review mode.

ReviewActivityDiagram

3.3.2. Design Considerations

Aspect: Tracking index of current card
  • Alternative 1 (current choice): Store a currentIndex field in UniqueCardList

    • Pros: Easy to implement and complies with separation of concerns. Can also easily saved in Storage.

    • Cons: Introducing state to UniqueCardList may not be the best solution.

  • Alternative 2: Store currentCardIndex in Anakin

    • Pros: Convenient access to field by exposing method in Model

    • Cons: Field is not required by many operations in Anakin and its use case is specific to deck reviews.

Aspect: Iterating through cards during review
  • Alternative 1 (current choice): Post events for UI to change the currently reviewed card.

    • Pros: Logic of iterating cards is decoupled from UI.

    • Cons: Limited scalability as adding new functionality require adding more events and event handlers.

  • Alternative 2: Implement a ListElementPointer for the list of cards similar to command history.

    • Pros: Able to easily implement more functionality such as keyboard shortcuts for iterating cards.

    • Cons: Still requires events to trigger changes in UI.

3.4. Performance Tracking Implementation

This section details the implementation details and considerations of the performance tracking feature.

3.4.1. Current Implementation

Cards contain the Performance parameter, implemented as an enum, as well as a timesReviewed counter. The fields are set to normal difficulty and 0 counts respectively on initialization. Since these are metadata fields, they will be considered optional and a Card can still be created without specifying them, which will result in the fields being reset to the default initializations.

The user can change the difficulty of a card during "review" mode. Users can only do this on the card they are currently reviewing, as it is the only time it makes sense to do so. Cards assigned a difficulty will have their timesReviewed counter incremented by one.

The command available to the user will be classify DIFFICULTY, where DIFFICULTY is any of the strings easy, normal, hard, corresponding to the level of perceived difficulty for that flashcard.

Given below is an example usage scenario for the performance tracking feature.

Step 1. The user requests to "review" a deck. He sees the question of card A from the deck.

Step 2. The user requests to see the answer. He sees the displayed answer.

Step 3. The user executes classify hard. Card A is now assigned the difficulty of hard.

Step 4. The user continues reviewing the rest of the cards in the deck.

Step 5. Card A is shown to the user more regularly when he reviews the same deck in the future.

3.5. UI Implementation

3.5.1. Previous implementation (v1.2)

The UI for Anakin at v1.2 split the main area into three sections:

  • (Left) List of decks

  • (Right) List of cards in selected deck

This was implemented by morphing PersonListPanel and PersonListCard into our use cases for decks and cards and editing MainWindow to render changes in both decks and cards.

Given below is an example usage scenario and how the lists are displayed at each step.

Step 1. The user launches Anakin application and sees a list of sample decks. At this step, the CardListPanel is empty.

Step 2. The user executes cd 1 command to navigate into the first deck. Anakin renders the deck’s cards (at index 1) on the CardListPanel.

Step 3. The user executes cd 2 command to navigate into the second deck. Anakin switches the displayed cards with that of the second deck.

Step 4. The user executes cd .. command to navigate out of the second deck. The CardListPanel is empty again.

3.5.2. Current implementation (v1.3)

The UI for Anakin was revamped in v1.3. Previously in v1.2, the application had a list of decks and list of cards on its main view MainWindow. In v1.3, the panels displaying these lists have been refactored into DeckEditScreen. This is because of the addition of DeckReviewScreen which acts as the user interface when users are reviewing a deck. When the user starts reviewing a deck, MainWindow will swap DeckEditScreen with DeckReviewScreen to show the correct UI. The DeckReviewScreen displays a DeckReviewCard at a time, as the user is going through his flashcards. It also boasts of the ability to flip the card to display questions and answers separately, and iterate to subsequent and previous cards in the deck. See Deck Review Implementation for an example use case.

3.5.3. Design considerations

  • Alternative 1 (current choice): Display deck and card lists side by side

    • Pros: Easy to implement

    • Cons: Somewhat lacking in aesthetics

  • Alternative 2: Display deck and card list in the same panel and switch out accordingly

    • Pros: Looks more impressive in UI-wise

    • Cons: Have to implement a switch event to toggle items inside StackPane

3.6. Keep cards panel updated by UI

3.6.1. Problem with the old design

The UI (MainWindow) constructs the displayed cards panel by obtaining an ObservableList of cards from Model, this list is assigned when UI starts, and will never be re-assigned.

The UI "observes" the list and updates when it is modified.

This approach works well for the deck list because Anakin contains only 1 list of decks. However, the card list can not be updated in the same manner because Model component will change its card list’s reference when user enters another deck.

In this case, the card list in UI will not be updated because the card list of which UI has reference to is actually not changed, but it is the wrong card list.

3.6.2. Design considerations

  • Alternative 1 (current choice): Have a displayedCards list in Model and keep it updated with the current list of cards

    • Explanation: The UI needs only 1 reference to this displayedCards list, each time user enters another deck, displayedCards list is cleared and the new deck’s list of cards is copy to the displayedCards list

    • Pros: The structure of Model and UI component needs not be changed

    • Cons: Need to keep a copy of the current card list, copying the whole list of cards for each cd operation might affect performance

  • Alternative 2: Model component raises an event when its current card list’s reference is changed

    • Explanation: When user cd (enters) a new deck, Model will raise an event (CardListChangedEvent), which is subscribed by UI, then UI can re-assign its list of cards and update the cards panel accordingly

    • Pros: Better performance

    • Cons: Need to re-design Model and UI components

3.7. Import/Export Implementation

3.7.1. Current implementation (v1.3)

Imports and exports in Anakin are managed by a PortManager.

Exporting a Deck will create an XmlExportableDeck, which is exported as a .xml file in the same folder as the Anakin.jar file.
Upon creation, Model will initialize a PortManager. When an ExportCommand or ImportCommand is executed, it will call the Model’s importDeck or exportDeck method.
PortManager uses the java.nio Path package to navigate directories.

3.7.2. Design considerations

  • Alternative 1 (current choice): Have Model contain a PortManager class to manage imports and export.

    • Explanation: Commands pass their arguments to Model, which passes arguments to the PortManager. In the case of ExportCommand, the Deck to be exported is passed from Command to Model to PortManager. PortManager returns a String of the export location, which is passed to Model, then passed to the Command for printing.

    • Pros: Better modularity and Separation of Concerns

    • Cons: Have to pass messages through many layers and methods.

  • Alternative 2: Have Model itself manage imports and exports

    • Explanation: Create methods in Model that directly handle conversion, imports, and exports.

    • Pros: Less message passing between layers

    • Cons: Worse modularity and Separation of Concerns.

  • Alternative 3: Use a 3rd party library to assist in managing imports/exports

    • Pros: Potentially more powerful functionality.

    • Pros: Good Separation of Concerns as the library is entirely modular.

    • Cons: Need to create methods to adapt data to be compatible with the library API.

    • Cons: Functionality is obscured. May be difficult to fix any unforseen errors.

3.8. Logging

We are using java.util.logging package for logging. The LogsCenter class is used to manage the logging levels and logging destinations.

  • The logging level can be controlled using the logLevel setting in the configuration file (See Section 3.11, “Configuration”)

  • The Logger for a class can be obtained using LogsCenter.getLogger(Class) which will log messages according to the specified logging level

  • Currently log messages are output through: Console and to a .log file.

Logging Levels

  • SEVERE : Critical problem detected which may possibly cause the termination of the application

  • WARNING : Can continue, but with caution

  • INFO : Information showing the noteworthy actions by the App

  • FINE : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size

3.9. Autocomplete Implementation

3.9.1. Current implementation (v1.4)

The logic behind autocompletion is handled by an Autocompleter class.

On pressing <TAB>, Command Box will raise an event and check if the current text in the command box is autocompletable, that is to say, it is a prefix of one of the existing commands supported by Anakin. If it is Autocompletable, Autocompleter will search through the list of existing commands in lexicographic order and find the first match for the current text in the command box.

3.9.2. Design considerations

  • Alternative 1 (current choice): Use a set of pre-decided completion text for each command and have a list of all supported command words

    • Explanation: Each command has a pre-decided AUTOCOMPLETE_TEXT field and we do prefix-matching between the text in the command box and our existing set of command words. If there is a potential match, we replace the current text in the command box with the AUTOCOMPLETE_TEXT of the supported command which it is matched to.

    • Pros: Provides better modularity by decreasing the depndency of autocomplete on external components (Current Implementation)

    • Cons: Less personalisation to each user. This design doesn’t take into account the past commands that the user has issued.

  • Alternative 2: Match current command against the history of previously executed commands

    • Pros: Better personalisation of each command

    • Cons: Worse modularity and separation of concerns as the autocompleter would need to interact with the history. As such, it might increase coupling between the autocomplete and history components.

3.9.3. How to add autocompletion for new commands

  • Should there be a need to include new commands, you can follow the following steps to ensure your command can be autocompleted.

Example Scenario: Suppose we just introduced the undo command and we wish to integrate it with the autocompleter.

Step 1 : Add the AUTOCOMPLETE_TEXT field to the class. This will decide what the command autocompletes to when the user presses tab.

AddAutocompleteTextExample

Step 2 : Add the class to Autocompleter java

AddCommandToAutocompleterExample

With that, you’re good to go!

3.10. Card Scheduling implementation

3.10.1. Current implementation (v1.4)

The logic behind scheduling is handled in Card.java. Each card contains reviewScore and nextReviewDate fields which determine how well the user remembers the card and when the user should review the card again respectively. When each card is classified using classify, it’s score is adjusted according to a modified version of the Super Memo 2 algorithm which we have detailed below. The review score is then updated, and using this review score we calculate the number of days to add to the current review date.

3.10.2. Design considerations

  • Alternative 1 (current choice): Implement the scheduling update by attaching the scheduling process to the card class

    • Pros: As only cards are meant to be scheduled, this will increase cohesion by strengthening the relationship between cards and the scheduling process.

    • Cons: Should there be a need to implement scheduling of decks, then we will need to refactor our code for scheduling.

  • Alternative 2: Create a separate scheduler class to handle performance operations.

    • Pros: This will allow us to schedule multiple type of objects.

    • Cons: This will decrease cohesion of the components as we only intend for cards to be scheduled in this application.

3.10.3. Scheduling Algorithm details

  • The variant of the Super Memo 2 algorithm which we have implemented in this application works as follows:

SuperMemoTwo

Image : A description of the Super Memo 2 variant we implemented. Formula taken from Blue Raja’s blog

Performance Rating:
How the user assesses his/her performance during classify.
easy corresponds to a 0 before scaling
normal corresponds to a 1 before scaling
hard corresponds to a 2 before scaling

Easiness Score : This corresponds to reviewScore in our implementation

As we only have three levels of difficulty, we have added a BIAS term to scale the reviewScore.

3.11. Configuration

Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file (default: config.json).

4. Documentation

We use asciidoc for writing documentation.

We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting.

4.1. Editing Documentation

See UsingGradle.adoc to learn how to render .adoc files locally to preview the end result of your edits. Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your .adoc files in real-time.

4.2. Publishing Documentation

See UsingTravis.adoc to learn how to deploy GitHub Pages using Travis.

4.3. Converting Documentation to PDF format

We use Google Chrome for converting documentation to PDF format, as Chrome’s PDF engine preserves hyperlinks used in webpages.

Here are the steps to convert the project documentation files to PDF format.

  1. Follow the instructions in UsingGradle.adoc to convert the AsciiDoc files in the docs/ directory to HTML format.

  2. Go to your generated HTML files in the build/docs folder, right click on them and select Open withGoogle Chrome.

  3. Within Chrome, click on the Print option in Chrome’s menu.

  4. Set the destination to Save as PDF, then click Save to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below.

chrome save as pdf
Figure 4. Saving documentation as PDF files in Chrome

4.4. Site-wide Documentation Settings

The build.gradle file specifies some project-specific asciidoc attributes which affects how all documentation files within this project are rendered.

Attributes left unset in the build.gradle file will use their default value, if any.
Table 1. List of site-wide attributes
Attribute name Description Default value

site-name

The name of the website. If set, the name will be displayed near the top of the page.

not set

site-githuburl

URL to the site’s repository on GitHub. Setting this will add a "View on GitHub" link in the navigation bar.

not set

site-seedu

Define this attribute if the project is an official SE-EDU project. This will render the SE-EDU navigation bar at the top of the page, and add some SE-EDU-specific navigation items.

not set

4.5. Per-file Documentation Settings

Each .adoc file may also specify some file-specific asciidoc attributes which affects how the file is rendered.

Asciidoctor’s built-in attributes may be specified and used as well.

Attributes left unset in .adoc files will use their default value, if any.
Table 2. List of per-file attributes, excluding Asciidoctor’s built-in attributes
Attribute name Description Default value

site-section

Site section that the document belongs to. This will cause the associated item in the navigation bar to be highlighted. One of: UserGuide, DeveloperGuide, LearningOutcomes*, AboutUs, ContactUs

* Official SE-EDU projects only

not set

no-site-header

Set this attribute to remove the site navigation bar.

not set

4.6. Site Template

The files in docs/stylesheets are the CSS stylesheets of the site. You can modify them to change some properties of the site’s design.

The files in docs/templates controls the rendering of .adoc files into HTML5. These template files are written in a mixture of Ruby and Slim.

Modifying the template files in docs/templates requires some knowledge and experience with Ruby and Asciidoctor’s API. You should only modify them if you need greater control over the site’s layout than what stylesheets can provide. The SE-EDU team does not provide support for modified template files.

5. Testing

5.1. Running Tests

There are three ways to run tests.

The most reliable way to run tests is the 3rd one. The first two methods might fail some GUI tests due to platform/resolution-specific idiosyncrasies.

Method 1: Using IntelliJ JUnit test runner

  • To run all tests, right-click on the src/test/java folder and choose Run 'All Tests'

  • To run a subset of tests, you can right-click on a test package, test class, or a test and choose Run 'ABC'

Method 2: Using Gradle

  • Open a console and run the command gradlew clean allTests (Mac/Linux: ./gradlew clean allTests)

See UsingGradle.adoc for more info on how to run tests using Gradle.

Method 3: Using Gradle (headless)

Thanks to the TestFX library we use, our GUI tests can be run in the headless mode. In the headless mode, GUI tests do not show up on the screen. That means the developer can do other things on the Computer while the tests are running.

To run tests in headless mode, open a console and run the command gradlew clean headless allTests (Mac/Linux: ./gradlew clean headless allTests)

5.2. Types of tests

We have two types of tests:

  1. GUI Tests - These are tests involving the GUI. They include,

    1. System Tests that test the entire App by simulating user actions on the GUI. These are in the systemtests package.

    2. Unit tests that test the individual components. These are in seedu.address.ui package.

  2. Non-GUI Tests - These are tests not involving the GUI. They include,

    1. Unit tests targeting the lowest level methods/classes.
      e.g. seedu.address.commons.StringUtilTest

    2. Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
      e.g. seedu.address.storage.StorageManagerTest

    3. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
      e.g. seedu.address.logic.LogicManagerTest

5.3. Troubleshooting Testing

Problem: HelpWindowTest fails with a NullPointerException.

  • Reason: One of its dependencies, HelpWindow.html in src/main/resources/docs is missing.

  • Solution: Execute Gradle task processResources.

6. Dev Ops

6.1. Build Automation

See UsingGradle.adoc to learn how to use Gradle for build automation.

6.2. Continuous Integration

We use Travis CI and AppVeyor to perform Continuous Integration on our projects. See UsingTravis.adoc and UsingAppVeyor.adoc for more details.

6.3. Coverage Reporting

We use Coveralls to track the code coverage of our projects. See UsingCoveralls.adoc for more details.

6.4. Documentation Previews

When a pull request has changes to asciidoc files, you can use Netlify to see a preview of how the HTML version of those asciidoc files will look like when the pull request is merged. See UsingNetlify.adoc for more details.

6.5. Making a Release

Here are the steps to create a new release.

  1. Update the version number in MainApp.java.

  2. Generate a JAR file using Gradle.

  3. Tag the repo with the version number. e.g. v0.1

  4. Create a new release using GitHub and upload the JAR file you created.

6.6. Managing Dependencies

A project often depends on third-party libraries. For example, Address Book depends on the Jackson library for XML parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives.
a. Include those libraries in the repo (this bloats the repo size)
b. Require developers to download those libraries manually (this creates extra work for developers)

Appendix A: Product Scope

Target user profile:

User: Student who uses flashcards heavily in their studies but finds trouble managing too many physical cards.

  • has a need to manage a significant number of flashcards

  • prefers desktop over other media (i.e. mobile)

  • can type quickly

  • prefers typing over mouse input

  • is reasonably comfortable using CLI apps

Value proposition: Manage flashcards faster than a typical mouse/GUI driven app.
Without any UI elements, the UI is extremely clean. Users will never accidentally click on something wrongly as there is never any functional response to mouse input.

Appendix B: User Stories

Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *

Priority As a …​ I want to …​ So that I can…​

* * *

new user

see usage instructions

refer to instructions when I forget how to use the App

* * *

user

add a new deck

* * *

user

add cards to a deck

* * *

user

delete a card

remove flashcards that I no longer need

* * *

user

find a deck by name

locate a deck without having to go through the entire list

* * *

user

iterate through decks

iterate viewing a deck of flashcards

* *

user with many flashcards

filter flashcards by tag

locate a flashcard easily

* *

user

rate my flashcards

identify weak areas for later revision

* *

user with multiple devices

import/ export decks to/ from external files

manage Anakin decks and cards across devices

*

user that types quickly

custom keyboard shortcuts

bind commands to my own aliases

Appendix C: Use Cases

(For all use cases below, the System is Anakin and the Actor is the user, unless specified otherwise)

C.1. List decks

MSS

  1. User requests to list decks

  2. Anakin shows a list of decks

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

C.2. Create a deck

MSS

  1. User requests to create deck

  2. Anakin prompts for deck details

  3. User enters deck details

  4. Anakin creates the deck

    Use case ends.

Extensions

  • 3a. User enters name of existing deck

    • 3a1. Anakin displays an error message.

    • 3a2. Anakin prompts for deck details.

      Use case resumes at step 4.

C.3. Delete a deck

MSS

  1. User requests to list decks

  2. Anakin shows a list of decks

  3. User requests to delete a specific deck in the list

  4. Anakin deletes the deck

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • 3a1. Anakin displays an error message.

      Use case resumes at step 2.

C.4. Add a card to a deck

MSS

  1. User selects a deck

  2. Anakin displays deck details

  3. User requests to add flashcard to the current deck

  4. Anakin prompts for flashcard details

  5. User enters requested details

  6. Anakin adds flashcard to the current deck

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 4a. User enters details in the wrong format

    • 4a1. Anakin displays error message

    • 4a2. Anakin prompts for flashcard details

    • 4a3. If details are in the wrong format, repeat steps 1-2

      Use case ends

C.5. Delete a card from a deck

MSS

  1. User selects a deck

  2. Anakin displays deck details

  3. User requests to delete a card.

  4. Anakin deletes the specified from the current deck

    Use case ends.

Extensions

  • 3a. The deck is empty.

  • 3a1. Anakin displays error message.

    Use case ends.

  • 3a. The index specified for the card does not exist.

    • 3a1. Anakin displays error message

      Use case resumes at step 3.

C.6. Locate a deck

MSS

  1. User requests to display any decks that match their search query.

  2. Anakin returns the list of decks that match the search query.

    Use case ends.

Extensions

  • 1a. The deck does not exist

    • 1a1. Anakin returns an empty list of decks

      Use case ends.

C.7. Locate a card

MSS

  1. User requests to display any cards that match their search query.

  2. Anakin returns the list of cards that match the search query.

    Use case ends.

Extensions

  • 1a. The card does not exist

    • 1a1. Anakin returns an empty list of cards

      Use case ends.

C.8. Export a deck

MSS

  1. User requests to export a deck

  2. Anakin exports the deck to the same directory as the Anakin.jar file.

    Use case ends.

Extensions

  • 1a. The deck does not exist at the index specified

    • 1a1. Anakin displays error message

      Use case ends.

C.9. Import a deck

MSS

  1. User requests to import a deck at a specified file location

  2. Anakin imports the deck

    Use case ends.

Extensions

  • 1a. The file does not exist at the location specified

    • 1a1. Anakin displays error message

      Use case ends.

  • 1a. The file exists but has the wrong format

    • 1a1. Anakin displays error message

      Use case ends.

Appendix D: Instructions for Manual Testing

Given below are instructions to test Anakin manually. These instructions only provide a starting framework for testers; testers are expected to do additional, exploratory testing.

D.1. Launch

Initial launch

  1. Download the jar file and copy into an empty folder

  2. Double-click the jar file to launch Anakin Expected: Shows the GUI with a set of sample decks.

Prerequisites: There must be at least 1 deck in the application.

  1. cd INDEX_OF_DECK where INDEX_OF_DECK is the currently displayed index of the target deck.

  2. The card panel on the right of the deck panel should be populated with a list of the cards in the target deck. If the deck is empty, no cards will be shown.

D.3. Exporting a deck

Exporting a deck from Anakin to a .xml file. Prerequisites: There must be at least 1 deck in the application.

  1. Enter the command export INDEX_OF_DECK where INDEX_OF_DECK is the currently displayed index of the target deck.

  2. Check the directory that contains Anakin.jar. There should be a .xml file with the same name as the target deck.

D.4. Importing a deck

Importing a deck from a .xml file at a known file location Prerequisites: Target deck must not already exist in Anakin. Renaming an existing deck is acceptable.

  1. Enter the command import DECK_FILEPATH where DECK_FILEPATH is the relative filepath of the deck from the folder that Anakin.jar is in.

  2. Anakin should display an import success message and the deck should appear in the deck panel.

  3. Navigating into the deck should display all the cards of that deck. (Assuming the deck was not empty)

  4. Performing the same operation again should display an error that states "This deck already exist in Anakin"

Importing a file that is in the same folder as Anakin.jar Prerequisites: Target must be in the same folder as Anakin.jar. Target deck must not already exist in Anakin. Renaming an existing deck is acceptable.

  1. Enter the command import DECK_NAME where DECK_NAME is the name of the .xml file.

  2. Anakin should display an import success message and the deck should appear in the deck panel.

  3. Navigating into the deck should display all the cards of that deck. (Assuming the deck was not empty)

  4. Performing the same operation again should display an error that states "This deck already exist in Anakin"

Importing from an invalid location

  1. Enter import BAD_FILEPATH where BAD_FILEPATH is a non-existent location.

  2. Anakin should display an error stating "File at BAD_FILEPATH could not be found".

Importing an invalid file

  1. Enter the command import DECK_FILEPATH where DECK_FILEPATH is the relative filepath of a corrupted or invalid from the folder that Anakin.jar is in.

  2. Anakin should display an error stating "Target deck contains invalid data".

Appendix E: Non Functional Requirements

  1. Should work on any mainstream OS as long as it has Java 9 or higher installed.

  2. Should be usable on a laptop with average hardware

  3. Internal state should be persistent

  4. Internal state should be recoverable from system crashes

  5. Should be able to hold up to 1000 flashcards without a noticeable sluggishness in performance for typical usage.

  6. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.

  7. Interactions should be smooth (<2 seconds response time)

  8. Should be usable by a command-line novice

  9. Navigation should be fast for expert user

  10. Internal state should be modifiable by expert user

  11. Internal state should be exportable and importable

  12. Should support formatting languages (e.g. Markdown, LaTEX)

Appendix F: Glossary

Deck

A collection of flashcards.

Flashcard

A card containing a prompt on one side, and a small amount of information (the answer) on the other

Internal State

The data of the application which includes decks, cards, and the order they are in but excludes session based information like Command History.
Maintained by Model and stored in Storage.

Markdown

A markup language with plain text formatting syntax.

Mainstream OS

Windows, Linux, Unix, OS-X