diff --git a/.github/check-eof-newline.sh b/.github/check-eof-newline.sh
index b771f3988dd..436c77aa818 100755
--- a/.github/check-eof-newline.sh
+++ b/.github/check-eof-newline.sh
@@ -1,5 +1,6 @@
#!/bin/sh
# Checks that all text files end with a newline.
+echo "Checking for newline at EOF..."
ret=0
diff --git a/.github/check-line-endings.sh b/.github/check-line-endings.sh
index 3de67ea87f6..c5759f006c0 100755
--- a/.github/check-line-endings.sh
+++ b/.github/check-line-endings.sh
@@ -1,6 +1,7 @@
#!/bin/sh
# Checks for prohibited line endings.
# Prohibited line endings: \r\n
+echo "Checking for prohibited line endings..."
git grep --cached -I -n --no-color -P '\r$' -- ':/' |
awk '
diff --git a/.github/check-trailing-whitespace.sh b/.github/check-trailing-whitespace.sh
index 33841caa81f..005d652c288 100755
--- a/.github/check-trailing-whitespace.sh
+++ b/.github/check-trailing-whitespace.sh
@@ -1,5 +1,6 @@
#!/bin/sh
# Checks for trailing whitespace
+echo "Checking for trailing whitespace..."
git grep --cached -I -n --no-color -P '[ \t]+$' -- ':/' |
awk '
@@ -9,16 +10,8 @@ awk '
ret = 0
}
{
- # Only warn for markdown files (*.md) to accomodate text editors
- # which do not properly handle trailing whitespace.
- # (e.g. GitHub web editor)
- if ($1 ~ /\.md$/) {
- severity = "WARN"
- } else {
- severity = "ERROR"
- ret = 1
- }
- print severity, $1, $2, " trailing whitespace."
+ ret = 1
+ print "ERROR", $1, $2, " trailing whitespace."
}
END {
exit ret
diff --git a/.github/run-checks.sh b/.github/run-checks.sh
index 7aad1e96220..13c55c6435e 100755
--- a/.github/run-checks.sh
+++ b/.github/run-checks.sh
@@ -1,6 +1,7 @@
#!/bin/sh
# Runs all check-* scripts, and returns a non-zero exit code if any of them fail.
+echo "Running formatting checks..."
dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) &&
ret=0 &&
for checkscript in "$dir"/check-*; do
diff --git a/.github/workflows/build-jekyll.yml b/.github/workflows/build-jekyll.yml
new file mode 100644
index 00000000000..87d7ee812dc
--- /dev/null
+++ b/.github/workflows/build-jekyll.yml
@@ -0,0 +1,49 @@
+name: Build and Deploy to GitHub Pages
+
+on:
+ push:
+ branches:
+ - master
+
+defaults:
+ run:
+ working-directory: docs
+
+jobs:
+ build_and_deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ # Set up Ruby
+ - uses: actions/setup-ruby@v1
+ with:
+ ruby-version: '2.7'
+
+ # Cache dependencies on server
+ - uses: actions/cache@v2
+ with:
+ path: vendor/bundle
+ key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-gems-
+
+ # Install dependencies
+ - name: Install dependencies
+ run: |
+ gem install bundler
+ bundle config path vendor/bundle
+ bundle install --jobs 4 --retry 3
+
+ # Build Jekyll site
+ - name: Build
+ env:
+ JEKYLL_ENV: production
+ run: bundle exec jekyll build --baseurl /${{ github.event.repository.name }} # Set the repo name as the base URL
+
+ # Deploy to GitHub Pages
+ - name: Deploy
+ uses: peaceiris/actions-gh-pages@v3
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./docs/_site
diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index ce1a0f882e0..1be2e161bf9 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -43,3 +43,41 @@ jobs:
with:
file: ${{ github.workspace }}/build/reports/jacoco/coverage/coverage.xml
fail_ci_if_error: true
+
+ pitest:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Set up repository
+ uses: actions/checkout@master
+
+ - name: Set up repository
+ uses: actions/checkout@master
+ with:
+ ref: master
+
+ - name: Merge to master
+ run: git checkout --progress --force ${{ github.sha }}
+
+ - name: Run repository-wide tests
+ if: runner.os == 'Linux'
+ working-directory: ${{ github.workspace }}/.github
+ run: ./run-checks.sh
+
+ - name: Validate Gradle Wrapper
+ uses: gradle/wrapper-validation-action@v1
+
+ - name: Setup JDK 11
+ uses: actions/setup-java@v1
+ with:
+ java-version: '11'
+ java-package: jdk+fx
+
+ - name: Run Pitest
+ run: ./gradlew pitest
+
+ - name: Upload Pitest report
+ uses: actions/upload-artifact@v2
+ with:
+ name: pitest
+ path: build/reports/pitest/
diff --git a/.gitignore b/.gitignore
index 71c9194e8bd..343123b3e27 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@ src/main/resources/docs/
/.idea/
/out/
/*.iml
+src/main/main.iml
# Storage/log files
/data/
@@ -20,3 +21,4 @@ src/test/data/sandbox/
# MacOS custom attributes files created by Finder
.DS_Store
docs/_site/
+docs/.sass-cache/
diff --git a/README.md b/README.md
index 13f5c77403f..752631576f4 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,26 @@
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
+[![CI Status](https://github.com/AY2021S1-CS2103T-W16-3/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2021S1-CS2103T-W16-3/tp/actions)
+[![codecov](https://codecov.io/gh/AY2021S1-CS2103T-W16-3/tp/branch/master/graph/badge.svg)](https://codecov.io/gh/AY2021S1-CS2103T-W16-3/tp)
![Ui](docs/images/Ui.png)
-* This is **a sample project for Software Engineering (SE) students**.
- Example usages:
- * as a starting point of a course project (as opposed to writing everything from scratch)
- * as a case study
-* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details.
- * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big.
- * It comes with a **reasonable level of user and developer documentation**.
-* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...).
-* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**.
-* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info.
+* Fine$$e is a desktop app for **managing finances, optimized for use via a Command Line Interface (CLI)** while still having the benefits of a Graphical User Interface (GUI). If you can type fast, Fine\$\$e can track and help you cultivate good financial habits faster than traditional GUI apps.
+
+Site Map
+==
+
+* [User Guide](https://ay2021s1-cs2103t-w16-3.github.io/tp/UserGuide.html)
+* [Developer Guide](https://ay2021s1-cs2103t-w16-3.github.io/tp/DeveloperGuide.html)
+* [About Us](https://ay2021s1-cs2103t-w16-3.github.io/tp/AboutUs.html)
+
+Acknowledgments
+==
+
+* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org).
+* Libraries used:
+ * [Git Hooks Gradle Plugin](https://plugins.gradle.org/plugin/com.github.jakemarsden.git-hooks) *(licensed under the [MIT License](https://github.com/jakemarsden/git-hooks-gradle-plugin/blob/master/LICENSE))*
+ * [Gradle plugin for PIT Mutation Testing](https://plugins.gradle.org/plugin/info.solidsoft.pitest)
+ * [JavaFX](https://openjfx.io/)
+ * [Jackson](https://github.com/FasterXML/jackson)
+ * [Jekyll Spaceship](https://rubygems.org/gems/jekyll-spaceship) *(licensed under the [MIT License](https://github.com/jakemarsden/git-hooks-gradle-plugin/blob/master/LICENSE))*
+ * [JUnit5](https://github.com/junit-team/junit5)
+ * [PlantUML](https://plantuml.com/)
diff --git a/build.gradle b/build.gradle
index be2d2905dde..688521e5734 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,9 +4,11 @@ plugins {
id 'com.github.johnrengelman.shadow' version '4.0.4'
id 'application'
id 'jacoco'
+ id 'info.solidsoft.pitest' version '1.5.2'
+ id 'com.github.jakemarsden.git-hooks' version '0.0.2'
}
-mainClassName = 'seedu.address.Main'
+mainClassName = 'ay2021s1_cs2103_w16_3.finesse.Main'
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
@@ -30,9 +32,9 @@ task coverage(type: JacocoReport) {
classDirectories.from files(sourceSets.main.output)
executionData.from files(jacocoTestReport.executionData)
afterEvaluate {
- classDirectories.from files(classDirectories.files.collect {
- fileTree(dir: it, exclude: ['**/*.jar'])
- })
+ classDirectories.setFrom(files(classDirectories.files.collect {
+ fileTree(dir: it, exclude: ['ay2021s1_cs2103_w16_3/finesse/ui/**/*', '**/*.jar'])
+ }))
}
reports {
html.enabled = true
@@ -40,6 +42,22 @@ task coverage(type: JacocoReport) {
}
}
+pitest {
+ targetClasses = ['ay2021s1_cs2103_w16_3.finesse.*']
+ junit5PluginVersion = '0.12'
+ threads = 4
+ excludedClasses = ['ay2021s1_cs2103_w16_3.finesse.ui.*']
+}
+
+gitHooks {
+ hooksDirectory = file('.git/hooks')
+ gradleCommand = './gradlew'
+ hooks = [
+ // Git hooks run in a bash environment regardless of OS.
+ 'pre-push': 'clean check && .github/run-checks.sh'
+ ]
+}
+
dependencies {
String jUnitVersion = '5.4.0'
String javaFxVersion = '11'
@@ -56,6 +74,12 @@ dependencies {
implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win'
implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac'
implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux'
+ implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'linux'
+ implementation group: 'org.openjfx', name: 'javafx-web', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-web', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-web', version: javaFxVersion, classifier: 'linux'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0'
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4'
@@ -66,7 +90,11 @@ dependencies {
}
shadowJar {
- archiveName = 'addressbook.jar'
+ archiveFileName = 'finesse.jar'
+}
+
+run {
+ enableAssertions = true
}
defaultTasks 'clean', 'test'
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index 4c001417aea..10731793567 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -91,7 +91,7 @@
-
+
diff --git a/config/travis/check-eof-newline.sh b/config/travis/check-eof-newline.sh
deleted file mode 100755
index b771f3988dd..00000000000
--- a/config/travis/check-eof-newline.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/sh
-# Checks that all text files end with a newline.
-
-ret=0
-
-# Preserve filename with spaces by only splitting on newlines.
-IFS='
-'
-
-for filename in $(git grep --cached -I -l -e '' -- ':/'); do
- if [ "$(tail -c 1 "./$filename")" != '' ]; then
- line="$(wc -l "./$filename" | cut -d' ' -f1)"
- echo "ERROR:$filename:$line: no newline at EOF."
- ret=1
- fi
-done
-
-exit $ret
diff --git a/config/travis/check-line-endings.sh b/config/travis/check-line-endings.sh
deleted file mode 100755
index 3de67ea87f6..00000000000
--- a/config/travis/check-line-endings.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/sh
-# Checks for prohibited line endings.
-# Prohibited line endings: \r\n
-
-git grep --cached -I -n --no-color -P '\r$' -- ':/' |
-awk '
- BEGIN {
- FS = ":"
- OFS = ":"
- ret = 0
- }
- {
- ret = 1
- print "ERROR", $1, $2, " prohibited \\r\\n line ending, use \\n instead."
- }
- END {
- exit ret
- }
-'
diff --git a/config/travis/check-trailing-whitespace.sh b/config/travis/check-trailing-whitespace.sh
deleted file mode 100755
index 33841caa81f..00000000000
--- a/config/travis/check-trailing-whitespace.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/sh
-# Checks for trailing whitespace
-
-git grep --cached -I -n --no-color -P '[ \t]+$' -- ':/' |
-awk '
- BEGIN {
- FS = ":"
- OFS = ":"
- ret = 0
- }
- {
- # Only warn for markdown files (*.md) to accomodate text editors
- # which do not properly handle trailing whitespace.
- # (e.g. GitHub web editor)
- if ($1 ~ /\.md$/) {
- severity = "WARN"
- } else {
- severity = "ERROR"
- ret = 1
- }
- print severity, $1, $2, " trailing whitespace."
- }
- END {
- exit ret
- }
-'
diff --git a/config/travis/run-checks.sh b/config/travis/run-checks.sh
deleted file mode 100755
index 7aad1e96220..00000000000
--- a/config/travis/run-checks.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/sh
-# Runs all check-* scripts, and returns a non-zero exit code if any of them fail.
-
-dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) &&
-ret=0 &&
-for checkscript in "$dir"/check-*; do
- if ! "$checkscript"; then
- ret=1
- fi
-done
-exit $ret
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 1c9514e966a..ccc8bb523ff 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -5,55 +5,55 @@ title: About Us
We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg).
-You can reach us at the email `seer[at]comp.nus.edu.sg`
+You can reach us at the email
## Project team
-### John Doe
+### Ian Yong
-
+
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](https://github.com/ianyong)]
+[[portfolio](team/ianyong.md)]
-* Role: Project Advisor
+* Role: Team Co-Lead
+* Responsibilities: Testing
-### Jane Doe
+### Tan Wei Liang
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/wltan)]
+[[portfolio](team/wltan.md)]
-* Role: Team Lead
-* Responsibilities: UI
+* Role: Team Co-Lead
+* Responsibilities: Git/CI Expert, Integration
-### Johnny Doe
+### Siddarth Nandanahosur Suresh
-
+
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](http://github.com/siddarth2824)] [[portfolio](team/siddarth2824.md)]
* Role: Developer
-* Responsibilities: Data
+* Responsibilities: Scheduling and Tracking, User Interface
-### Jean Doe
+### Zhao Jingjing
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/zhaojj2209)]
+[[portfolio](team/zhaojj2209.md)]
* Role: Developer
-* Responsibilities: Dev Ops + Threading
+* Responsibilities: Documentation, Model, Logic
-### James Doe
+### Yong Ping
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/yongping827)]
+[[portfolio](team/yongping827.md)]
* Role: Developer
-* Responsibilities: UI
+* Responsibilities: Code Quality, Storage
diff --git a/docs/Configuration.md b/docs/Configuration.md
deleted file mode 100644
index 13cf0faea16..00000000000
--- a/docs/Configuration.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-layout: page
-title: Configuration guide
----
-
-Certain properties of the application can be controlled (e.g user preferences file location, logging level) through the configuration file (default: `config.json`).
diff --git a/docs/DevOps.md b/docs/DevOps.md
deleted file mode 100644
index 4414eea3344..00000000000
--- a/docs/DevOps.md
+++ /dev/null
@@ -1,79 +0,0 @@
----
-layout: page
-title: DevOps guide
----
-
-* Table of Contents
-{:toc}
-
---------------------------------------------------------------------------------------------------------------------
-
-## Build automation
-
-This project uses Gradle for **build automation and dependency management**. **You are recommended to read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html)**.
-
-
-Given below are how to use Gradle for some important project tasks.
-
-
-* **`clean`**: Deletes the files created during the previous build tasks (e.g. files in the `build` folder).
- e.g. `./gradlew clean`
-
-* **`shadowJar`**: Uses the ShadowJar plugin to creat a fat JAR file in the `build/lib` folder, *if the current file is outdated*.
- e.g. `./gradlew shadowJar`.
-
-* **`run`**: Builds and runs the application.
- **`runShadow`**: Builds the application as a fat JAR, and then runs it.
-
-* **`checkstyleMain`**: Runs the code style check for the main code base.
- **`checkstyleTest`**: Runs the code style check for the test code base.
-
-* **`test`**: Runs all tests.<
- * `./gradlew test` — Runs all tests
- * `./gradlew clean test` — Cleans the project and runs tests
-
---------------------------------------------------------------------------------------------------------------------
-
-## Continuous integration (CI)
-
-This project uses GitHub Actions for CI. The project comes with the necessary GitHub Actions configurations files (in the `.github/workflows` folder). No further setting up required.
-
-### Code coverage
-
-As part of CI, this project uses Codecov to generate coverage reports. Here are the steps to set up CodeCov for a fork of this repository.
-
-1. Sign up with Codecov using your GitHub account [here](https://codecov.io/signup).
-1. Once you are inside Codecov web app, add your fork to CodeCov.
-1. Get the Markdown code for the Codecov badge provided in `Settings > Badges` and update the `docs/index.md` of your repo with it so that the badge [![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) in that page reflects the coverage of your project.
-
-### Repository-wide checks
-
-In addition to running Gradle checks, CI includes some repository-wide checks. Unlike the Gradle checks which only cover files used in the build process, these repository-wide checks cover all files in the repository. They check for repository rules which are hard to enforce on development machines such as line ending requirements.
-
-These checks are implemented as POSIX shell scripts, and thus can only be run on POSIX-compliant operating systems such as macOS and Linux. To run all checks locally on these operating systems, execute the following in the repository root directory:
-
-`./config/travis/run-checks.sh`
-
-Any warnings or errors will be printed out to the console.
-
-**If adding new checks:**
-
-* Checks are implemented as executable `check-*` scripts within the `.github` directory. The `run-checks.sh` script will automatically pick up and run files named as such. That is, you can add more such files if you need and the CI will do the rest.
-
-* Check scripts should print out errors in the format `SEVERITY:FILENAME:LINE: MESSAGE`
- * SEVERITY is either ERROR or WARN.
- * FILENAME is the path to the file relative to the current directory.
- * LINE is the line of the file where the error occurred and MESSAGE is the message explaining the error.
-
-* Check scripts must exit with a non-zero exit code if any errors occur.
-
---------------------------------------------------------------------------------------------------------------------
-
-## Making a release
-
-Here are the steps to create a new release.
-
-1. Update the version number in [`MainApp.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java).
-1. Generate a fat JAR file using Gradle (i.e., `gradlew shadow`).
-1. Tag the repo with the version number. e.g. `v0.1`
-1. [Create a new release using GitHub](https://help.github.com/articles/creating-releases/). Upload the JAR file you created.
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 4829fe43011..7054afffffe 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -7,12 +7,6 @@ title: Developer Guide
--------------------------------------------------------------------------------------------------------------------
-## **Setting up, getting started**
-
-Refer to the guide [_Setting up and getting started_](SettingUp.md).
-
---------------------------------------------------------------------------------------------------------------------
-
## **Design**
### Architecture
@@ -21,28 +15,22 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md).
The ***Architecture Diagram*** given above explains the high-level design of the App. Given below is a quick overview of each component.
-
-
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/se-edu/addressbook-level3/tree/master/docs/diagrams/) folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams.
-
-
-
-**`Main`** has two classes called [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). 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 methods where necessary.
+**`Main`** has two classes called [`Main`](https://github.com/AY2021S1-CS2103T-W16-3/tp/blob/master/src/main/java/ay2021s1_cs2103_w16_3/finesse/Main.java) and [`MainApp`](https://github.com/AY2021S1-CS2103T-W16-3/tp/blob/master/src/main/java/ay2021s1_cs2103_w16_3/finesse/MainApp.java). It is responsible for:
+* on app launch - initializing the components in the correct sequence, and connecting them to each other.
+* on shut down - shutting down the components and invoking cleanup methods where necessary.
[**`Commons`**](#common-classes) represents a collection of classes used by multiple other components.
The rest of the App consists of four components.
-* [**`UI`**](#ui-component): The UI of the App.
+* [**`UI`**](#ui-component): The App's User Interface.
* [**`Logic`**](#logic-component): The command executor.
* [**`Model`**](#model-component): Holds the data of the App in memory.
* [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk.
-Each of the four components,
+Each of the four components:
-* defines its *API* in an `interface` with the same name as the Component.
+* defines its *API* in an `interface` with the same name as the component.
* exposes its functionality using a concrete `{Component Name}Manager` class (which implements the corresponding API `interface` mentioned in the previous point.
For example, the `Logic` component (see the class diagram given below) defines its API in the `Logic.java` interface and exposes its functionality using the `LogicManager.java` class which implements the `Logic` interface.
@@ -51,7 +39,7 @@ For example, the `Logic` component (see the class diagram given below) defines i
**How the architecture components interact with each other**
-The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`.
+The *sequence diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`.
@@ -62,31 +50,43 @@ The sections below give more details of each component.
![Structure of the UI Component](images/UiClassDiagram.png)
**API** :
-[`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java)
+[`Ui.java`](https://github.com/AY2021S1-CS2103T-W16-3/tp/blob/master/src/main/java/ay2021s1_cs2103_w16_3/finesse/ui/Ui.java)
-The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class.
+The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `TransactionListPanel`, `OverviewTabPane` etc.
+All of these, including `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`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml)
+There are 5 tab panes which are contained within `MainWindow`:
+* `OverviewTabPane` - Displays an overview of the most recent transactions (both incomes and expenses) along with the savings goals.
+ * Contains `TransactionListPanel` and `SavingsGoalPanel`.
+* `IncomeTabPane` - Displays a list of incomes along with bookmark incomes.
+ * Contains `IncomePanel` and `BookmarkIncomePanel`.
+* `ExpenseTabPane` - Displays a list of expenses along with bookmark expenses.
+ * Contains `ExpensePanel` and `BookmarkExpensePanel`.
+* `AnalyticsTabPane` - Displays statistics.
+* `UserGuideTabPane` - Displays the user guide.
-The `UI` component,
+The `UI` component uses the JavaFx UI framework. The layout of these UI parts is defined in matching `.fxml` files that are in the `src/main/resources/view` folder.
+For example, the layout of [`MainWindow`](https://github.com/AY2021S1-CS2103T-W16-3/tp/blob/master/src/main/java/ay2021s1_cs2103_w16_3/finesse/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2021S1-CS2103T-W16-3/tp/blob/master/src/main/resources/view/MainWindow.fxml).
-* Executes user commands using the `Logic` component.
-* Listens for changes to `Model` data so that the UI can be updated with the modified data.
+The `UI` component:
+
+* executes user commands using the `Logic` component.
+* listens for changes to `Model` data so that the UI can be updated with the modified data.
### Logic component
![Structure of the Logic Component](images/LogicClassDiagram.png)
**API** :
-[`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java)
+[`Logic.java`](https://github.com/AY2021S1-CS2103T-W16-3/tp/blob/master/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/Logic.java)
-1. `Logic` uses the `AddressBookParser` class to parse the user command.
-1. This results in a `Command` object which is executed by the `LogicManager`.
-1. The command execution can affect the `Model` (e.g. adding a person).
-1. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`.
-1. In addition, the `CommandResult` object can also instruct the `Ui` to perform certain actions, such as displaying help to the user.
+1. `Logic` uses the `FinanceTrackerParser` class to parse the user command.
+1. This results in a `Command` object which is executed by `LogicManager`.
+1. The command execution can affect `Model` (e.g. adding a transaction).
+1. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to `Ui`.
+1. In addition, the `CommandResult` object can also instruct `Ui` to perform certain actions, such as displaying the help message to the user.
-Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call.
+Below is the sequence diagram for interactions within the `Logic` component for the `execute("delete 1")` API call.
![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png)
@@ -97,35 +97,91 @@ Given below is the Sequence Diagram for interactions within the `Logic` componen
![Structure of the Model Component](images/ModelClassDiagram.png)
-**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java)
+**API** : [`Model.java`](https://github.com/AY2021S1-CS2103T-W16-3/tp/blob/master/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/Model.java)
-The `Model`,
+The `Model`:
* stores a `UserPref` object that represents the user’s preferences.
-* stores the address book data.
-* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.
-* does not depend on any of the other three components.
-
+* stores a `CommandHistory` object that keeps track of the 50 most recent commands entered.
+* stores the finance tracker data in the following components:
+ * a `TransactionList` containing `Transaction`s.
+ * a `BookmarkExpenseList` and `BookmarkIncomeList`, each containing `BookmarkTransaction`s.
+ * a `MonthlyBudget`.
+* exposes `MonthlyBudget`, which can be 'observed' e.g. the UI can be bound to the values in the class so that the UI automatically updates when the values in the class changes.
+* exposes an unmodifiable `ObservableList`, `ObservableList`, `ObservableList`, `ObservableList` and `ObservableList` each, which can be 'observed' e.g. the UI can be 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 (`UI`, `Logic`, `Storage`).
+
+A `Transaction`:
+* represents a unit of user data of transactions within the finance tracker.
+* is either an `Expense` or an `Income`.
+* is composed of a `Title`, an `Amount`, a `Date`, and any number of `Category`s. These are known as *data fields*.
+
+A `BookmarkTransaction`:
+* represents a unit of user data of bookmark transactions within the finance tracker.
+* is either a `BookmarkExpense` or a `BookmarkIncome`.
+* is composed of a `Title`, an `Amount`, and any number of `Category`s.
+
+The *data fields* `Title`, `Amount`, `Date`, and `Category` are encapsulations of an underlying Java data type.
+
+| Data Field | Underlying Java Data Type |
+| ----------- | ------------------------- |
+| `Title` | `java.lang.String` |
+| `Amount` | `java.math.BigDecimal` |
+| `Date` | `java.time.LocalDate` |
+| `Category` | `java.lang.String` |
+
+The underlying Java data types allow more operations to be done on `Transaction` objects, such as filtering `Transaction`s by `Date`, or aggregating the `Amount`s of `Expense`s and `Income`s.
+
+
:information_source:
+**Note:** All data fields take in a `String` in their constructor, regardless of the underlying Java data type.
+Within the constructor, data validation takes place to ensure that the `String` correctly represents a valid data field.
+If the `String` given is not valid, an `IllegalArgumentException` will be thrown.
+Otherwise, the `String` is parsed into the underlying Java data type.
+The reason for this is that when a data field object needs to be created, it is generally from a `String`
+(such as when a user command parsed from the `Logic` component, or when a transaction is loaded from the `Storage` component).
+This abstraction is maintained so that the implementation of other components (`Logic` and `Storage`) is independent of
+the underlying Java data type choices in the `Model` component.
+
-
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object.
-![BetterModelClassDiagram](images/BetterModelClassDiagram.png)
+The `FinanceTracker` has the following fields:
+* a `TransactionList` field which stores all `Expense` and `Income` objects together.
+* a `BookmarkExpenseList` field which stores all `BookmarkExpense` objects.
+* a `BookmarkIncomeList` field which stores all `BookmarkIncome` objects.
+* a `MonthlyBudget` field which stores the user's set monthly expense limit and monthly savings goal, as well as all calculated values for the user's statistics.
-
+The `ModelManager` has the following fields:
+* three `FilteredList` fields which point to the same `ObservableList` obtained from `FinanceTracker::getTransactionList`. The `Predicate` fields in the three `FilteredList` fields are set such that:
+ * `filteredTransactions` shows a view of all `Transaction` objects.
+ * `filteredExpenses` shows a view of all `Transaction` objects of type `Expense`.
+ * `filteredIncomes` shows a view of all `Transaction` objects of type `Income`.
+ The motivation behind having three lists is due to the fact that there are three tabs in the user interface which display transactions, each having its own list while at the same time retrieving data from the same transaction list.
+* two `ObservableList` fields, `castFilteredExpenses` and `castFilteredIncomes` that contain the same objects as that in `filteredExpenses` and `filteredIncomes` respectively. This is so that the UI can automatically update when the values in the two lists change.
+* two `FilteredList` fields, `FilteredBookmarkExpenses` and `FilteredBookmarkIncomes` that point to the `ObservableList`s obtained from `FinanceTracker::getBookmarkExpenseList` and `FinanceTracker::getBookmarkIncomeList` respectively.
+* a `MonthlyBudget` field which is the same as the `MonthlyBudget` stored in `FinanceTracker`.
### Storage component
![Structure of the Storage Component](images/StorageClassDiagram.png)
-**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java)
+**API** : [`Storage.java`](https://github.com/AY2021S1-CS2103T-W16-3/tp/blob/master/src/main/java/ay2021s1_cs2103_w16_3/finesse/storage/Storage.java)
-The `Storage` component,
-* can save `UserPref` objects in json format and read it back.
-* can save the address book data in json format and read it back.
+The `Storage` component:
+
+* can save `UserPref` objects in `JSON` format and read it back.
+* can save the finance tracker data in `JSON` format and read it back.
+
+`JsonAdaptedExpense` and `JsonAdaptedIncome` are `JSON`-friendly adaptations of `Expense` and `Income` respectively.
+However, unlike `Expense` and `Income`, all the fields in `JsonAdaptedExpense` and `JsonAdaptedIncome` are `String` objects.
+This is so that the fields are compatible with the `JSON` format.
+
+`JsonSerializableFinanceTracker` contains two `List` fields, one containing all `JsonAdaptedExpense` objects and the other containing all `JsonAdaptedIncome` objects.
+Despite the similarities between `Expense` and `Income`, and despite the fact that objects of both types are put into the same list in `FinanceTracker`,
+the motivation behind keeping them separately is to ensure that they are separately listed in the `JSON` data file.
### Common classes
-Classes used by multiple components are in the `seedu.addressbook.commons` package.
+Classes used by multiple components are in the `ay2021s1_cs2103_w16_3.finesse.commons` package.
--------------------------------------------------------------------------------------------------------------------
@@ -133,145 +189,643 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa
This section describes some noteworthy details on how certain features are implemented.
-### \[Proposed\] Undo/redo feature
+### Tabs
-#### Proposed Implementation
+#### Programmatically switch selected tab
-The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations:
+##### Overview
-* `VersionedAddressBook#commit()` — Saves the current address book state in its history.
-* `VersionedAddressBook#undo()` — Restores the previous address book state from its history.
-* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history.
+Fine$$e supports the ability to switch tabs programmatically.
+The following is a list of commands that utilise this ability:
+* `help` - Opens the [user guide](UserGuide.md).
+* `tab` - Switches to the tab corresponding to the index entered by the user.
+* `add-expense` - Adds an expense to the finance tracker and switches to the Expenses tab.
+* `ls-expense` - Shows a list of all expenses in the finance tracker by switching to the Expenses tab.
+* `add-income` - Adds an income to the finance tracker and switches to the Incomes tab.
+* `ls-income` - Shows a list of all incomes in the finance tracker by switching to the Incomes tab.
+* `add-bookmark-expense` - Adds a bookmark expense to the finance tracker and switches to the Expenses tab.
+* `add-bookmark-income` - Adds a bookmark income to the finance tracker and switches to the Incomes tab.
-These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
+##### Implementation of feature
-Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
+The ability to programmatically update the selected tab is implemented via `CommandResult`.
+Upon the successful execution of a command, a `CommandResult` is generated and passed back to `MainWindow`.
+This `CommandResult` contains the necessary information which the `MainWindow` needs to update the user interface, such as:
+* The feedback to be displayed to the user;
+* Whether to exit the application; and
+* Whether to programmatically switch to a different tab in the user interface.
-Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state.
+`MainWindow` will then select the tab specified in `CommandResult` as the active tab if applicable.
-![UndoRedoState0](images/UndoRedoState0.png)
+##### Switching tabs
-Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state.
+The following sequence diagram shows how the `CommandResult` is created and returned upon the execution of any command.
+If the command in question is one of those listed above, the UI will switch to the specified tab.
-![UndoRedoState1](images/UndoRedoState1.png)
+![Interactions inside the Logic Component when executing commands](images/CommandResultSequenceDiagram.png)
-Step 3. The user executes `add n/David …` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`.
+##### Design considerations
-![UndoRedoState2](images/UndoRedoState2.png)
+The alternative implementations considered, as well as the rationale behind our current implementation are as follows:
-
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`.
+| Alternative considered | Current implementation and rationale |
+| ----------- | ------------------------- |
+| Add a method in `MainWindow` which can be called to programmatically switch tabs in the user interface upon execution of the command. | Encapsulate the tab switching information within `CommandResult` to prevent tight coupling of `Logic` and `UI` components. |
-
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state.
+### Transactions
-![UndoRedoState3](images/UndoRedoState3.png)
+#### Add transactions feature
-
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather
-than attempting to perform the undo.
+##### Overview
+The add transactions feature allows users to add transactions into the `FinanceTracker`.
+Each new transaction must have the data fields `Title`, `Amount`, `Date`, and `Category`.
-
+Below is the class diagram of the components involved in the add transactions feature.
-The following sequence diagram shows how the undo operation works:
+![Class diagram for add transactions feature](images/AddTransactionClassDiagram.png)
-![UndoSequenceDiagram](images/UndoSequenceDiagram.png)
+##### Implementation of feature
-
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+The add transactions feature is implemented via `AddExpenseCommand` and `AddIncomeCommand`, which are created from `AddExpenseCommandParser` and `AddIncomeCommandParser` respectively.
-
+1. `AddExpenseCommandParser` and `AddIncomeCommandParser` take in the argument string and parses it into an `ArgumentMultimap` that contains all the different data fields mapped (as strings) to their respective prefix.
+1. The strings are then parsed to create the data fields within the Model component (dependency arrows omitted in the above diagram for simplicity).
+1. The parsers use the data fields to create `Expense` or `Income` objects, which are then used to create `AddExpenseCommand` or `AddIncomeCommand` objects.
-The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state.
+`FinanceTrackerParser` decides whether `AddExpenseCommandParser` or `AddIncomeCommandParser` is used.
+* On an `add-expense` or `adde` command, `AddExpenseCommandParser` is used.
+* On an `add-income` or `addi` command, `AddIncomeCommandParser` is used.
+* On an `add` command:
+ * `AddExpenseCommandParser` is used if the user is currently on the Expenses tab.
+ * `AddIncomeCommandParser` is used if the user is currently on the Incomes tab.
+ * A `ParseException` is thrown if the user is currently on neither of the above tabs.
-
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
+##### Adding transactions
-
+Below is the sequence diagram for interactions within the `Logic` and `Model` components when the user inputs the `adde t/Bubble Tea a/5 d/03/10/2020 c/Food & Beverage` command while on the Expenses tab.
+Note that the arguments in the full command string have been abbreviated to `...`.
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged.
+![Sequence diagram for executing the `add-expense t/Bubble Tea a/5 d/03/10/2020 c/Food & Beverage` command on the Expenses tab](images/AddTransactionSequenceDiagram.png)
-![UndoRedoState4](images/UndoRedoState4.png)
+When `AddIncomeCommandParser` is used, the sequence diagram is similar, but all occurrences of `Expense` are replaced with `Income`.
-Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …` command. This is the behavior that most modern desktop applications follow.
+The following activity diagram summarizes what happens within `FinanceTrackerParser` when the user executes a new add command:
-![UndoRedoState5](images/UndoRedoState5.png)
+![Activity diagram for executing the add command](images/AddTransactionActivityDiagram.png)
-The following activity diagram summarizes what happens when a user executes a new command:
+*Note: The diagram contains many repetitive processes. However, it cannot be simplified due to the limitations of PlantUML.*
-![CommitActivityDiagram](images/CommitActivityDiagram.png)
+##### Design considerations
-#### Design consideration:
+The alternative implementations considered, as well as the rationale behind our current implementation are as follows:
-##### Aspect: How undo & redo executes
+| Alternative considered | Current implementation and rationale |
+| ----------- | ------------------------- |
+| Collapse both `AddExpenseCommandParser` and `AddIncomeCommandParser` into a single `AddTransactionCommandParser` whose result can then be used to create `AddExpenseCommand` or `AddIncomeCommand`. | Keep two separate parsers `AddExpenseCommandParser` and `AddIncomeCommandParser`. Although the `add` command can result in either `AddExpenseCommand` or `AddIncomeCommand`, `adde` will always create `AddExpenseCommand` while `addi` will always create `AddIncomeCommand`. It is more apt to use a specific parser in the latter cases. |
+| Implement either `add` or `adde` + `addi` but not both. This reduces the amount of development work needed. | Both options are implemented to provide the user with a choice for whichever command they feel is most convenient. `add` can be used to add to the currently active tab, while `adde` and `addi` can be used on any tab. |
-* **Alternative 1 (current choice):** Saves the entire address book.
- * Pros: Easy to implement.
- * Cons: May have performance issues in terms of memory usage.
-* **Alternative 2:** Individual command knows how to undo/redo by
- itself.
- * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
- * Cons: We must ensure that the implementation of each individual command are correct.
+#### Edit transactions feature
+##### Overview
+The edit transactions feature allows users to edit existing transactions in the `FinanceTracker`.
+At least one of the data fields `Title`, `Amount`, `Date`, and `Category` must be modified.
-_{more aspects and alternatives to be added}_
+Below is the class diagram of the components involved in the edit transactions feature.
-### \[Proposed\] Data archiving
+![Class diagram for edit transactions feature](images/EditTransactionClassDiagram.png)
-_{Explain here how the data archiving feature will be implemented}_
+##### Implementation of feature
+The edit transactions feature is implemented via `EditCommandParser`, which returns an `EditCommand` that is then used to create `EditExpenseCommand` or `EditIncomeCommand`.
---------------------------------------------------------------------------------------------------------------------
+1. `EditCommandParser` takes in the argument string and parses it into an `ArgumentMultimap` that contains all the different data fields mapped (as strings) to their respective prefix.
+1. The index is parsed into an `Index`, which is used to locate the transaction to be modified during command execution.
+1. The remaining strings are then parsed to create the data fields within the Model component (dependency arrows omitted in the above diagram for simplicity).
+1. The data fields are used to create an `EditTransactionDescriptor` describing all of the new data fields.
+1. The `Index` is combined with the `EditTransactionDescriptor` to return an `EditCommand`.
-## **Documentation, logging, testing, configuration, dev-ops**
+`FinanceTrackerParser` decides whether `EditExpenseCommand` or `EditIncomeCommand` is created from the resulting `EditCommand`.
+* `EditExpenseCommand` is used if the user is currently on the Expenses tab.
+* `EditIncomeCommand` is used if the user is currently on the Incomes tab.
-* [Documentation guide](Documentation.md)
-* [Testing guide](Testing.md)
-* [Logging guide](Logging.md)
-* [Configuration guide](Configuration.md)
-* [DevOps guide](DevOps.md)
+##### Editing transactions
---------------------------------------------------------------------------------------------------------------------
+Below is the sequence diagram for interactions within the `Logic` and `Model` components when the user inputs the `edit 1 a/5` command while on the Expenses tab.
+It is split into two diagrams, one for parsing and one for execution.
+
+![Sequence diagram for parsing the `edit 1 a/5` command on the Expenses tab](images/EditTransactionSequenceDiagram.png)
+
+![Sequence diagram for executing the `edit 1 a/5` command on the Expenses tab](images/EditTransactionSequenceDiagram2.png)
+
+When `EditExpenseCommand` is used, the sequence diagram is similar, but all occurrences of `Expense` are replaced with `Income`.
+
+The following activity diagram summarizes what happens within `FinanceTrackerParser` when the user executes a new edit command:
+
+![Activity diagram for executing the edit command](images/EditTransactionActivityDiagram.png)
+
+#### Delete transactions feature
+##### Overview
+The delete transactions feature allows users to remove transactions from the `FinanceTracker`.
+
+Below is the class diagram of the components involved in the delete transactions feature.
+
+![Class diagram for delete transactions feature](images/DeleteTransactionClassDiagram.png)
+
+##### Implementation of feature
+
+The edit transactions feature is implemented via `DeleteCommandParser`, which returns an `DeleteCommand` that is then used to create `DeleteExpenseCommand` or `DeleteIncomeCommand`.
+
+1. `DeleteCommandParser` take in the argument string and parses the index into an `Index`, which is used to locate the transaction to be deleted during command execution.
+1. The `Index` is used to return a `DeleteCommand`.
+
+`FinanceTrackerParser` decides whether `DeleteExpenseCommand` or `DeleteIncomeCommand` is created from the resulting `DeleteCommand`.
+* `DeleteExpenseCommand` is used if the user is currently on the Expenses tab.
+* `DeleteIncomeCommand` is used if the user is currently on the Incomes tab.
+
+##### Deleting transactions
+
+Below is the sequence diagram for interactions within the `Logic` and `Model` components when the user inputs the `delete 1` command while on the Expenses tab.
+
+![Sequence diagram for parsing the `delete 1` command on the Expenses tab](images/DeleteSequenceDiagram.png)
+
+When `DeleteExpenseCommand` is used, the sequence diagram is similar, but all occurrences of `Expense` are replaced with `Income`.
+
+The following activity diagram summarizes what happens within `FinanceTrackerParser` when the user executes a new delete command:
+
+![Activity diagram for executing the delete command](images/DeleteTransactionActivityDiagram.png)
+
+#### Find transactions feature
+##### Overview
+
+The find transactions feature allows users to search for transactions in the `FinanceTracker` by various search parameters.
+Each search parameter corresponds to a different predicate which will be used to filter transactions in the various `FilteredList`s.
+
+Below is the class diagram of the components involved in the find transactions feature.
+
+![Class diagram for find transactions feature](images/FindClassDiagram.png)
+
+##### Implementation of feature
+
+The find transactions feature is implemented via `FindCommandParser`, as well as the following commands:
+
+* `FindCommand`, the base command that is returned when the command is parsed.
+* `FindTransactionCommand`, to be executed when the user inputs the command on the Overview tab.
+* `FindExpenseCommand`, to be executed when the user inputs the command on the Expenses tab.
+* `FindIncomeCommand`, to be executed when the user inputs the command on the Incomes tab.
+
+`FindCommandParser` takes in the argument string and parses it into an `ArgumentMultimap` that contains all the different search parameters mapped to their respective prefix.
+
+Depending on the parameters present, `FindCommandParser` then creates a `List>` containing the predicates that will be used to filter the transactions.
+It then returns a `FindCommand` containing the list of predicates.
+
+Depending on the UI tab the user inputted the command in, a `FindXYZCommand` (`FindTransactionCommand`, `FindExpenseCommand` or `FindIncomeCommand`) will be created from the base `FindCommand`.
+When executed, the `FindXYZCommand` will combine all of the predicates in the list into a `combinedPredicate`, then sets the predicate of the respective `FilteredList` in `ModelManager` so that only the transactions matching the `combinedPredicate` will be displayed in the UI.
+
+The list of predicates that can be used to filter the `FilteredList`s are as follows:
+* `TitleContainsKeyphrasesPredicate` checks if any of the given keyphrases is present in the transaction's title.
+* `HasExactAmountPredicate` checks if the transaction's amount is equal to the given amount.
+* `OnExactDatePredicate` checks if the transaction's date is equal to the given date.
+* `HasCategoriesPredicate` checks if the transaction's categories contains any of the given categories.
+* `InAmountRangePredicate` checks if the transaction's amount is within the given amount range.
+* `InDateRangePredicate` checks if the transaction's date is within the given date range.
+
+##### Finding transactions
+
+Below is the sequence diagram for interactions within the `Logic` and `Model` components when the user inputs the `"find t/tea a/5"` command while on the Overview tab.
+
+![Sequence diagram for executing the `find t/tea a/5` command on the Overview tab](images/FindSequenceDiagram.png)
+
+The following activity diagram summarizes what happens when the user executes a new find command:
+
+![Activity diagram for executing the find command](images/FindActivityDiagram.png)
+
+##### Design considerations
+
+The alternative implementations considered, as well as the rationale behind our current implementation are as follows:
+
+| Alternative considered | Current implementation and rationale |
+| ----------- | ------------------------- |
+| Having separate command parsers for each tab in which the find command can be input, e.g. `FindTransactionCommandParser`, `FindExpenseCommandParser` and `FindIncomeCommandParser`, which return a `FindTransactionCommand`, a `FindExpenseCommand` and a `FindIncomeCommand` respectively. | Use only one `FindCommandParser`, which returns a `FindCommand` that is then further split into the respective `FindXYZCommand`. This is because the parsing for the input is similar same regardless of the tab the user is on. |
+| Have `FindCommandParser` take in an `Index` corresponding to the parameter being searched. | Make the input of similar format to that of adding transactions, so that the input can be parsed into an `ArgumentMultimap` which is then used generate the relevant `Predicate`s. This is so that multiple search parameters can be employed in one command. |
+
+### Budgeting
+##### Overview
+
+The budgeting feature allows users to track their remaining budget and current savings based on their set monthly expense limit and monthly savings goal.
+The user sets the monthly expense limit and monthly savings goal, and the remaining budget and current savings will automatically be calculated based on the transactions in the `FinanceTracker`.
+
+The class diagram below depicts the components involved in the budget feature.
+
+![Class diagram for budgeting feature](images/BudgetClassDiagram.png)
+
+##### Implementation of feature
+
+The budgeting feature is implemented via `MonthlyBudget`, which contains the following fields:
+
+* Two `ObjectProperty` fields for `monthlyExpenseLimit` and `monthlySavingsGoal`.
+* Two `ObjectProperty` fields for `remainingBudget` and `currentSavings`.
+* Three `ObservableList` fields for `monthlyExpenses`, `monthlyIncomes` and `monthlySavings`, which are used for the [Analytics feature](#analytics).
+* An `ObservableList` of `months`, to determine which elements in the `ObservableList` corresponds to which month.
+
+`CalculatedAmount` differs from `Amount` in that a `CalculatedAmount` can be negative, and supports addition and subtraction.
+
+The method that is integral to the `MonthlyBudget` is the `calculateBudgetInfo` method.
+It takes in the current `TransactionList` in the `FinanceTracker` and an integer `numOfMonths`, and recalculates all `CalculatedAmount`s in the `MonthlyBudget` as follows:
+* Sum the amounts of all `Expense`s in the `TransactionList` dated in the current month, subtracts it from `monthlyExpenseLimit`, and sets it as the `remainingBudget`.
+* Sums the amounts of all `Expense`s in the `TransactionList` dated in the current month, subtracts it from the sum of all `Income`s dated in the current month, and sets it as the `currentSavings`.
+* For the past number of months indicated by `numOfMonths` (including the current month), calculate the total expenses, total incomes and total savings for each month and add them to the corresponding `ObservableList`.
+
+The commands that trigger a call of `calculateBudgetInfo` are as follows:
+* `AddExpenseCommand`, `AddIncomeCommand`, `DeleteExpenseCommand` and `DeleteIncomeCommand`, which change the total expenses/incomes of the `Transaction`s in the `TransactionList`.
+* `EditExpenseCommand` and `EditIncomeCommand`, if the `Amount` is edited.
+* `SetExpenseLimitCommand`, which changes the `Amount` in `monthlyExpenseLimit`.
+* `SetSavingsGoalCommand`, which changes the `Amount` in `monthlySavingsGoal`.
+
+`calculateBudgetInfo` is also called by `Storage` whenever the finance tracker data is loaded upon startup.
+
+##### Set monthly spending limit
+
+`SetExpenseLimitCommand` and `SetSavingsGoalCommand` work in similar ways.
+Below is the sequence diagram for interactions within the `Logic` and `Model` components when the user inputs the `"setel a/500"` command.
+
+![Sequence diagram for executing the `setel a/500` command](images/SetExpenseLimitSequenceDiagram.png)
+
+The following activity diagram summarizes what happens when the user executes any command that changes the calculated values in `MonthlyBudget` (e.g. setting expense limit):
+
+![Activity diagram for executing any command that changes the budget](images/BudgetActivityDiagram.png)
+
+##### Design considerations
+
+The alternative implementations considered, as well as the rationale behind our current implementation are as follows:
+
+| Alternative considered | Current implementation and rationale |
+| ----------- | ------------------------- |
+| Use the `Amount` class for calculated amounts. | Use a separate class `CalculatedAmount` for calculated amounts, so as to avoid breaking abstraction and support negative values.
+
+### Bookmark transaction
+
+A bookmark transaction is a template that allows users to create transactions that they make frequently, such as paying phone bills monthly or receiving stipend for being a teaching assistant.
+This feature reduces the hassle of keying in information repeatedly for identical transactions that occur frequently.
+
+A `BookmarkTransaction` is made up of 3 fields: `Title`, `Amount` and `Category`.
+The class diagram below depicts the structure of the `BookmarkTransaction`, `BookmarkExpense`, `BookmarkIncome`, `BookmarkExpenseList` and `BookmarkIncomeList` in the Model component.
+
+![Class Diagram for Bookmark Transaction Class](images/BookmarkTransactionClassDiagram.png)
+
+{:.image-caption}
+Class diagram for bookmark transaction model component
+
+#### Add bookmark transactions
+
+##### Overview
+
+The add bookmark transactions feature allows users to add bookmark transactions into the `FinanceTracker`.
+Each new bookmark transaction must have the data fields `Title`, `Amount`, and `Category`.
+
+Below is the class diagram of the components involved in the add transactions feature.
+
+![AddBookmarkTransaction Class Diagram](images/AddBookmarkTransactionClassDiagram.png)
+
+##### Implementation of feature
+
+The add bookmark transaction feature is implemented via `AddBookmarkExpenseCommand` and `AddBookmarkIncomeCommand`, which are created from `AddBookmarkExpenseCommandParser` and `AddBookmarkIncomeCommandParser` respectively.
+
+1. `AddBookmarkExpenseCommandParser` and `AddBookmarkIncomeCommandParser` take in the argument string and parses it into an `ArgumentMultiMap` that contains all the different data fields mapped (as strings) to their respective prefix.
+
+2. The strings are then parsed to create the data fields within the Model component (dependency arrows omitted in the above diagram for simplicity).
+
+3. The parsers use the data fields to create `BookmarkExpense` or `BookmarkIncome` objects, which are then used to create `AddBookmarkExpenseCommand` or `AddBookmarkIncomeCommand` objects.
+
+
+The following is a detailed elaboration of how `AddBookmarkExpenseCommand` operates.
+
+**Step 1**. After the successful parsing of user input, the `AddBookmarkExpenseCommand#execute(Model model)` method is executed.
+
+**Step 2**. `ModelManager#addBookmarkExpense(toAdd)` is invoked to add the new bookmark expense.
+
+**Step 3**. The new bookmark expense is added into `FinanceTracker#bookmarkExpenses`, via the `FinanceTracker#addBookmarkExpense(BookmarkExpense bookmarkExpense)`.
+
+**Step 4**. This will then call the `BookmarkExpenseList#add(BookmarkExpense toAdd)` method which will check if the title of the new bookmark expense already exists in `BookmarkExpenseList#internalBookmarkExpenseList` via `BookmarkExpenseList#contains(BookmarkExpense toCheck)` before adding it.
+If the title does exist already, the `DuplicateBookmarkTransactionException` will be thrown otherwise the new bookmark expense will be added.
+
+**Step 5**. After successfully adding the new bookmark expense, the command box will be reflected with `AddBookmarkExpenseCommand#MESSAGE_SUCCESS` constant and a new `CommandResult` will be returned with the message.
+
+![Sequence Diagram of AddBookmarkExpenseCommand](images/AddBookmarkExpenseSequenceDiagram.png)
+
+{:.image-caption}
+Sequence diagram for adding bookmark expenses
+
+![Sequence Diagram of parsing input to create AddBookmarkExpenseCommand](images/ParsingAddBookmarkExpenseInputSequenceDiagram.png)
+
+{:.image-caption}
+Reference frame for sequence diagram
+
+#### Edit bookmark transaction
+
+##### Overview
+
+The edit bookmark transaction feature allows users to edit the details of a specified bookmark transaction in the `FinanceTracker`.
+
+Below is the class diagram of the components involved in the edit bookmark transaction feature.
+
+![Class diagram for edit bookmark transaction](images/EditBookmarkTransactionClassDiagram.png)
+
+{:.image-caption}
+Class diagram of components involved in edit bookmark transaction feature
+
+##### Implementation of feature
+
+> :information_source: `EditBookmarkExpenseCommand` and `EditBookmarkIncomeCommand` work in similar ways.
+
+Following is a detailed elaboration of how `EditBoomarkExpenseCommand` operates.
+
+> :information_source: This command can only be executed on the Expenses tab.
+
+**Step 1**. After the successful parsing of user input, the `EditBookmarkExpenseCommand#execute(Model model)` method is called which checks if the `Index` defined as an argument when instantiating `EditBookmarkCommand(Index targetIndex, EditBookmarkTransactionDescriptor editBookmarkTransactionDescriptor)` is valid.
+It uses `EditBookmarkTransactionDescriptor` to create a new edited bookmark expense.
+Since it is optional for the users to input fields, the fields which are not entered will reuse the existing values that are currently stored and defined in the `BookmarkExpense` object.
+
+> :information_source: The `Index` must be within the bounds of the list of bookmark expenses.
+
+**Step 2**. A new `BookmarkExpense` with the newly updated attributes will be created which replaces the existing `BookmarkExpense` object using `Model#setBookmarkExpense(BookmarkExpense target, BookmarkExpense editedBookmarkExpense)` method.
+
+**Step 3**. The filtered bookmark expense list is then updated with the new `BookmarkExpense` with the `Model#updateFilteredBookmarkExpenseList(PREDICATE_SHOW_ALL_BOOKMARK_EXPENSES)` method.
+
+**Step 4**. The command box will be reflected with the `EditBookmarkExpenseCommand#MESSAGE_EDIT_BOOKMARK_EXPENSE_SUCCESS` constant and a new `CommandResult` will be returned with the success message.
+
+#### Delete bookmark transaction
+
+##### Overview
+
+The delete bookmark transaction feature allows users to delete a specified bookmark transaction in the `FinanceTracker`.
+
+Below is the class diagram of the components involved in the delete bookmark transaction feature.
+
+![Class diagram for delete bookmark transaction](images/DeleteBookmarkTransactionClassDiagram.png)
+
+{:.image-caption}
+Class diagram of components involved in delete bookmark transaction feature
+
+##### Implementation of feature
+
+> :information_source: `DeleteBookmarkExpenseCommand` and `DeleteBookmarkIncomeCommand` work in similar ways.
+
+Following is a detailed elaboration of how `DeleteBoomarkExpenseCommand` operates.
+
+> :information_source: This command can only be executed on the Expenses tab.
-## **Appendix: Requirements**
+**Step 1**. After the successful parsing of user input, the `DeleteBookmarkExpenseCommand#execute(Model model)` method is called which checks if the `Index` is defined when instantiating the `DeleteBookmarkCommand(Index index)` constructor is valid.
-### Product scope
+> :information_source: The `Index` must be within the bounds of the list of bookmark expenses.
+
+**Step 2**. The `BookmarkExpense` at the specified `Index` is then removed from the `BookmarkExpenseList#internalBookmarkExpenseList` observable list using the `Model#deleteBookmarkExpense(BookmarkExpense bookmarkExpense)` method.
+
+**Step 3**. The command box will be reflected with the `DeleteBookmarkExpenseCommand#MESSAGE_DELETE_BOOKMARK_EXPENSE_SUCCESS` constant and a new `CommandResult` will be returned with the success message.
+
+The following activity diagram summarizes what happens when the user executes any command that deletes a specified bookmark expense:
+
+![Activity diagram for delete bookmark expense command](images/DeleteBookmarkExpenseActivityDiagram.png)
+
+{:.image-caption}
+Activity diagram for deleting bookmark expense
+
+#### Convert bookmark transaction
+
+##### Overview
+
+The convert bookmark transaction feature converts a specified bookmark transaction into a transaction and adds it to the `FinanceTracker`.
+
+Below is the class diagram of the components involved in the convert bookmark transaction feature.
+
+![Class diagram for convert bookmark transaction command](images/ConvertBookmarkTransactionClassDiagram.png)
+
+{:.image-caption}
+Class diagram of components involved in convert bookmark transaction feature
+
+##### Implementation of feature
+
+> :information_source: `ConvertBookmarkExpenseCommand` and `ConvertBookmarkIncomeCommand` work in similar ways.
+
+Following is a detailed elaboration of how `ConvertBoomarkExpenseCommand` operates.
+
+> :information_source: This command can only be executed on the Expenses tab.
+
+**Step 1**. After the successful parsing of user input, the `ConvertBookmarkExpenseCommand#execute(Model model)` method is called which checks if the `Index` is defined when instantiating the `ConvertBookmarkCommand(Index index)` constructor is valid.
+
+> :information_source: The `Index` must be within the bounds of the list of bookmark expenses.
+
+**Step 2**. The list of bookmark expenses will then be retrieved by calling `Model#getFilteredBookmarkExpenseList()`.
+
+**Step 3**. The program will then retrieve the specified bookmark expense from the list of bookmark expenses. It will
+then call `BookmarkExpense#convert` on the bookmark expense together with the date the user has inputted and convert
+it to an `Expense` object.
+
+**Step 4**. After converting the `BookmarkExpense` object to an `Expense` object, it will call `Model#addExpense` to add the
+`Expense` object to the expense list in the finance tracker.
+
+**Step 5**. The command box will be reflected with the `ConvertBookmarkExpenseCommand#MESSAGE_CONVERT_BOOKMARK_EXPENSE_SUCCESS` constant and a new `CommandResult` will be returned with the success message.
+
+![Sequence Diagram of the Convert Frequent Expense To Expense Feature](images/ConvertBookmarkExpenseSequenceDiagram.png)
+
+{:.image-caption}
+Sequence diagram for converting bookmark expense to expense
+
+![Sequence Diagram of parsing input to create ConvertBookmarkExpenseCommand](images/ParsingConvertBookmarkExpenseInputSequenceDiagram.png)
+
+### Analytics
+
+##### Overview
+
+The Analytics feature allows the user to view their spending and saving trends over a period of time.
+Based on the current transaction lists, the total monthly expenses, incomes and savings over the past three months are calculated and displayed in the Analytics Tab in the form of bar charts.
+
+##### Implementation of feature
+
+![Object diagram for Analytics feature](images/AnalyticsTabObjectDiagram.png)
+
+When the constructor for `AnalyticsTabPane` is called:
+1. `AnalyticsTabPane` constructor takes in a `MonthlyBudget` object from `Model`.
+1. The `BarChart` objects (`expenseAnalyticsBarChart`, `incomeAnalyticsBarChart` and `savingsAnalyticsBarChart`) are constructed and `Axis` objects are added to them.
+1. The method `AnalyticsTabPane#populateData` is called and takes in the `MonthlyBudget` object.
+ The following fields from `MonthlyBudget` are accessed:
+ * `ObservableList` fields: `monthlyExpenses`, `monthlyIncomes` and `monthlySavings`
+ * `ObservableList` field: `months`
+
+ The method `AnalyticsTabPane#populateDataIn` is called for each `BarChart` object.
+1. In the method `AnalyticsTabPane#populateDataIn`, the values from the above fields in *step 3* are accessed to construct `XY.Data` objects.
+1. The `XY.Data` objects are added to the `BarChart` objects.
+
+##### Design considerations
+
+* An external library, [Data2Viz](https://data2viz.io/), was considered, rather than *JavaFX Charts*, but the former was rejected due to difficulty in implementation.
+
+### Command history
+
+##### Overview
+
+In order to fully replicate the Command Line Interface (CLI) experience, Fine$$e features the ability to navigate through the last 50 commands entered.
+This is done by pressing the ↑ or ↓ arrow keys on the keyboard while focused on the command input box.
+
+The class diagram below depicts the components involved in the command history feature.
+
+![Class diagram for command history feature](images/CommandHistoryClassDiagram.png)
+
+##### Implementation of feature
+
+The command history feature is implemented via `CommandHistory`.
+Whenever the user inputs a command, the command is added to the `CommandHistory` so as to be tracked.
+Note that for this section on the command history, all mentions of 'command' refer to the user input, and not the various `Command`s present in the codebase.
+
+`CommandHistory` is backed by an `EvictingStack`, a data structure that works similarly to a normal Last In First Out (LIFO) stack.
+However, when the `EvictingStack` is full, the bottom-most element of the stack is removed (or in other words, evicted).
+This behaviour is achieved by maintaining a doubly-linked list made out of `Node`s.
+
+Each `Node` keeps track of the following information:
+* Its value, which corresponds to a command in this context.
+* A reference to the next `Node` in the linked list.
+* A reference to the previous `Node` in the linked list.
+
+For the `EvictingStack`, it keeps track of not only the `Node` at the top of the stack, but also the `Node` at the bottom.
+This is so that the bottom-most element can be removed in Θ(1) time.
+Due to the linked list being doubly-linked, we can then also find the next bottom-most element of the `EvictingStack` in constant time.
+
+In addition, `CommandHistory` maintains an internal navigation state that gets reset whenever a new command is entered.
+This internal navigation state keeps track of the current position while traversing the command history using the ↑ and ↓ arrow keys, much like a typical CLI.
+
+##### Navigating the command history
+
+When the ↑ arrow key is pressed, there are two possible scenarios:
+1. The current command in the navigation state is the earliest tracked command.
+ * Nothing happens.
+1. The current command in the navigation state is not the earliest tracked command.
+ * The command immediately preceding the current command is retrieved.
+
+The following activity diagram summarizes what happens when the user presses the ↑ arrow key.
+
+![Activity diagram for pressing the ↑ arrow key](images/CommandHistoryUpActivityDiagram.png)
+
+Similarly, when the ↓ arrow key is pressed, there are two possible scenarios:
+1. The current command in the navigation state is the latest tracked command.
+ * The command input box is cleared.
+1. The current command in the navigation state is not the latest tracked command.
+ * The command immediately succeeding the current command is retrieved.
+
+The following activity diagram summarizes what happens when the user presses the ↓ arrow key.
+
+![Activity diagram for pressing the ↓ arrow key](images/CommandHistoryDownActivityDiagram.png)
+
+##### Design considerations
+
+The alternative implementations considered, as well as the rationale behind our current implementation are as follows:
+
+| Alternative considered | Current implementation and rationale |
+| ----------- | ------------------------- |
+| Use Java's in-built `Stack` class in `CommandHistory`. | Use a custom `EvictingStack` instead, so as to prevent potentially running out of memory if an extremely large number of commands are entered. |
+
+### Data integrity safeguards
+
+##### Overview
+
+Since Fine$$e is an offline desktop application, it is reliant on the system time to perform date validations.
+This makes it vulnerable to changes in the system time while it is running.
+In particular, if the system time were to be changed to an earlier time, the existing data might get corrupted.
+
+##### Implementation of feature
+
+To safeguard against potential loss or corruption of data, Fine$$e tracks the system time across actions via `Timekeeper`, which keeps track of the last observed time.
+Upon launching the application, `Timekeeper` is instantiated with the current system time.
+Subsequently, every time a command is entered by the user, the last observed time in `Timekeeper` is validated against the current system time.
+
+If the current system time is earlier than the last observed time, Fine\$\$e disables itself to prevent data loss.
+A prompt is shown to the user to ensure that their system time is correct before restarting Fine$$e in order to continue using the application.
+Upon relaunching the application, the data file is validated by `JsonFinanceTrackerParser`.
+
+Should the current system time be later than or equals to the last observed time, the last observed time in `Timekeeper` is updated to the current system time.
+
+##### Validating the time whenever a command is executed
+
+The following sequence diagram shows what happens when a command is entered by the user.
+
+![Sequence diagram for Timekeeper](images/TimekeeperSequenceDiagram.png)
+
+##### Design considerations
+
+The alternative implementations considered, as well as the rationale behind our current implementation are as follows:
+
+| Alternative considered | Current implementation and rationale |
+| ----------- | ------------------------- |
+| Query and use the time from some remote server rather than the system time. | Keep track of the last observed system time so that there is no need for an internet connection to use the application. |
+
+--------------------------------------------------------------------------------------------------------------------
+
+## **Appendix A: Product scope**
**Target user profile**:
-* has a need to manage a significant number of contacts
-* prefer desktop apps over other types
-* can type fast
-* prefers typing to mouse interactions
-* is reasonably comfortable using CLI apps
+* Tertiary student
+* Has a need to track everyday expenses/incomes
+* Wishes to cultivate good saving habits
+* Prefers desktop apps
+* Prefers typing to mouse interactions
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+**Value proposition**: Manage finances and cultivate good financial habits (such as saving) efficiently by typing in CLI commands
+--------------------------------------------------------------------------------------------------------------------
-### User stories
+## **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 person | |
-| `* * *` | user | delete a person | remove entries that I no longer need |
-| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list |
-| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident |
-| `*` | user with many persons in the address book | sort persons by name | locate a person easily |
+| 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 an expense | |
+| `* * *` | user | add an income | |
+| `* * *` | user | view a list of past transactions | keep track of my past expenses/incomes |
+| `* * *` | user | edit a transaction | update expenses/incomes that were entered wrongly |
+| `* * *` | user | delete a transaction | remove expenses/incomes that were entered wrongly |
+| `* * *` | user | group transactions by categories | keep track of my past expenses/incomes across various categories |
+| `* * *` | user | search for transactions | easily find related expenses/incomes |
+| `* * *` | user | bookmark expenses | easily add recurring expenses |
+| `* * *` | user | bookmark incomes | easily add recurring incomes |
+| `* *` | user | set a monthly spending limit | plan the maximum amount I wish to spend for the month |
+| `* *` | user | set a monthly saving goal | work towards saving consistently and reaching my savings goals |
+| `* *` | user | see my remaining monthly budget | track how much I have left to spend for the month |
+| `* *` | user | set a current monthly savings | track how much I have to save to hit my savings goal |
+| `* *` | user | view my saving trends | better plan my future expenses |
+
+--------------------------------------------------------------------------------------------------------------------
+
+## **Appendix C: Use cases**
+
+(For all use cases below, the **System** is `Fine$$e` and the **Actor** is the `user`, unless specified otherwise)
+
+**Use case: UC01 - Add an expense**
+
+**MSS**
+
+1. User requests to add an expense.
+2. Fine$$e adds the expense.
+
+ Use case ends.
+
+**Extensions**
-*{More to be added}*
+* 1a. The given data fields are invalid.
-### Use cases
+ * 1a1. Fine$$e shows an error message.
-(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+ Use case ends.
-**Use case: Delete a person**
+**Use case: UC02 - Edit an expense**
**MSS**
-1. User requests to list persons
-2. AddressBook shows a list of persons
-3. User requests to delete a specific person in the list
-4. AddressBook deletes the person
+1. User requests to list expenses.
+2. Fine$$e shows a list of expenses.
+3. User requests to edit a specific expense.
+4. Fine$$e edits the expense.
Use case ends.
@@ -283,74 +837,709 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli
* 3a. The given index is invalid.
- * 3a1. AddressBook shows an error message.
+ * 3a1. Fine$$e shows an error message.
Use case resumes at step 2.
-*{More to be added}*
+* 3b. No data fields are specified.
-### Non-Functional Requirements
+ * 3b1. Fine$$e shows an error message.
-1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
-2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
-3. 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.
+ Use case resumes at step 2.
-*{More to be added}*
+* 3c. The given data fields are invalid.
-### Glossary
+ * 3c1. Fine$$e shows an error message.
-* **Mainstream OS**: Windows, Linux, Unix, OS-X
-* **Private contact detail**: A contact detail that is not meant to be shared with others
+ Use case resumes at step 2.
---------------------------------------------------------------------------------------------------------------------
+**Use case: UC03 - Delete an expense**
-## **Appendix: Instructions for manual testing**
+**MSS**
-Given below are instructions to test the app manually.
+1. User requests to list expenses.
+2. Fine$$e shows a list of expenses.
+3. User requests to delete a specific expense.
+4. Fine$$e deletes the expense.
-
:information_source: **Note:** These instructions only provide a starting point for testers to work on;
-testers are expected to do more *exploratory* testing.
+ Use case ends.
-
+**Extensions**
-### Launch and shutdown
+* 2a. The list is empty.
-1. Initial launch
+ Use case ends.
- 1. Download the jar file and copy into an empty folder
+* 3a. The given index is invalid.
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+ * 3a1. Fine$$e shows an error message.
-1. Saving window preferences
+ Use case resumes at step 2.
- 1. Resize the window to an optimum size. Move the window to a different location. Close the window.
+**Use case: UC04 - Add an income**
- 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained.
+**MSS**
-1. _{ more test cases … }_
+1. User requests to add an income
+2. Fine$$e adds the income
-### Deleting a person
+ Use case ends.
-1. Deleting a person while all persons are being shown
+**Extensions**
- 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+* 1a. The given data fields are invalid.
- 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
+ * 1a1. Fine$$e shows an error message.
- 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
+ Use case ends.
- 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous.
+**Use case: UC05 - Edit an income**
-1. _{ more test cases … }_
+**MSS**
-### Saving data
+1. User requests to list incomes.
+2. Fine$$e shows a list of incomes.
+3. User requests to edit a specific income.
+4. Fine$$e edits the income.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. Fine$$e shows an error message.
+
+ Use case resumes at step 2.
+
+* 3b. No data fields are specified.
+
+ * 3b1. Fine$$e shows an error message.
+
+ Use case resumes at step 2.
+
+* 3c. The given data fields are invalid.
+
+ * 3c1. Fine$$e shows an error message.
+
+ Use case resumes at step 2.
+
+**Use case: UC06 - Delete an income**
+
+**MSS**
+
+1. User requests to list incomes.
+2. Fine$$e shows a list of incomes.
+3. User requests to delete a specific income.
+4. Fine$$e deletes the income.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. The given index is invalid.
+
+ * 3a1. Fine$$e shows an error message.
+
+ Use case resumes at step 2.
+
+**Use case: UC07 - Set the expense limit**
+
+**MSS**
+
+1. User requests to set the expense limit to a specific amount.
+2. Fine$$e sets the expense limit.
+3. Fine$$e recalculates budget information.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The given data field is invalid.
+
+ * 2a1. Fine$$e shows an error message.
+
+ Use case ends.
+
+**Use case: UC08 - Set the savings goal**
+
+**MSS**
+
+1. User requests to set the savings goal to a specific amount.
+2. Fine$$e sets the savings goal.
+3. Fine$$e recalculates budget information.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The given data field is invalid.
+
+ * 2a1. Fine$$e shows an error message.
+
+ Use case ends.
+
+**Use case: UC09 - Add Bookmark Expense**
+
+**MSS**
+
+1. User enters command, together with a title, expense amount and categories related to the bookmark expense.
+2. Fine$$e displays the feedback that a new bookmark expense has been added.
+3. Fine$$e bookmark expenses list panel will reflect the updated bookmark expenses list.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. Command is invalid.
+
+ * 1a1. Fine$$e shows an error message.
+
+ Use case ends.
+
+* 1b. Title of the new bookmark expense already exists in the bookmark expenses list.
+
+ * 1b1. Fine$$e shows an error message.
+
+ Use case ends.
+
+**Use case: UC10 - Edit Bookmark Expense**
+
+**MSS**
+
+1. User enters command, together with an index of the bookmark expense to edit, and fields to update.
+2. Fine$$e displays feedback that the specific bookmark expense chosen has been edited.
+3. Fine$$e bookmark expenses list will display the updated bookmark expenses list.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. Command is invalid.
+
+ * 1a1. Fine$$e shows an error message.
+
+ Use case ends.
+
+* 1b. Index given is invalid.
+
+ * 1b1. Fine$$e shows an error message.
+
+ Use case ends.
+
+* 1c. Title of edited expense already exists in the bookmark expenses list.
+
+ * 1c1. Fine$$e shows an error message.
+
+ Use case ends.
+
+**Use case: UC11 - Delete Bookmark Expense**
+
+**MSS**
+
+1. User enters command, together with index of the bookmark expense to delete.
+2. Fine$$e displays the feedback that the specified bookmark expense is deleted.
+3. Fine$$e’s bookmark expenses list will display the updated bookmark expenses list.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. Command is invalid.
+
+ * 1a1. Fine$$e shows an error message.
+
+ Use case ends.
+
+* 1b. Bookmark expenses list is empty.
+
+ * 1b1. Fine$$e shows an error message.
+
+ Use case ends.
+
+* 1c. Index given is invalid.
+
+ * 1c1. Fine$$e shows an error message.
+
+ Use case ends.
+
+**Use case: UC12 - Convert Bookmark Expense**
+
+**MSS**
+
+1. User enters command, together with the index of the bookmark expense to convert and the date of when the user intends to convert the bookmark expense to an expense.
+2. Fine$$e displays the feedback that the specified bookmark expense has been converted and added to the expenses list.
+3. Fine$$e’s expense list will display the updated expenses list.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. Command is invalid.
+
+ * 1a1. Fine$$e shows an error message.
+
+ Use case ends.
+
+* 1b. Bookmark expenses list is empty.
+
+ * 1b1. Fine$$e shows an error message.
+
+ Use case ends.
+
+* 1c. Index given is invalid.
+
+ * 1c1. Fine$$e shows an error message.
+
+ Use case ends.
+
+**Use case: UC13 - Add Bookmark Income**
+
+**MSS**
+
+1. User enters command, together with a title, income amount and categories related to the bookmark income.
+2. Fine$$e displays the feedback that a new bookmark income has been added.
+3. Fine$$e bookmark incomes list panel will reflect the updated bookmark incomes list.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. Command is invalid.
+
+ * 1a1. Fine$$e shows an error message.
+
+ Use case ends.
+
+* 1b. Title of the new bookmark income already exists in the bookmark incomes list.
+
+ * 1b1. Fine$$e shows an error message.
+
+ Use case ends.
+
+**Use case: UC14 - Edit Bookmark Income**
+
+**MSS**
+
+1. User enters command, together with an index of the bookmark income to edit, and fields to update.
+2. Fine$$e displays feedback that the specific bookmark income chosen has been edited.
+3. Fine$$e bookmark incomes list will display the updated bookmark incomes list.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. Command is invalid.
+
+ * 1a1. Fine$$e shows an error message.
+
+ Use case ends.
+
+* 1b. Index given is invalid.
+
+ * 1b1. Fine$$e shows an error message.
+
+ Use case ends.
+
+* 1c. Title of edited income already exists in the bookmark incomes list.
+
+ * 1c1. Fine$$e shows an error message.
+
+ Use case ends.
+
+**Use case: UC15 - Delete Bookmark Income**
+
+**MSS**
+
+1. User enters command, together with index of the bookmark income to delete.
+2. Fine$$e displays the feedback that the specified bookmark income is deleted.
+3. Fine$$e’s bookmark incomes list will display the updated bookmark incomes list.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. Command is invalid.
+
+ * 1a1. Fine$$e shows an error message.
+
+ Use case ends.
+
+* 1b. Bookmark incomes list is empty.
+
+ * 1b1. Fine$$e shows an error message.
+
+ Use case ends.
+
+* 1c. Index given is invalid.
+
+ * 1c1. Fine$$e shows an error message.
+
+ Use case ends.
+
+**Use case: UC16 - Convert Bookmark Income**
+
+**MSS**
+
+1. User enters command, together with the index of the bookmark income to convert and the date of when the user intends to convert the bookmark income to an income.
+2. Fine$$e displays the feedback that the specified bookmark income has been converted and added to the incomes list.
+3. Fine$$e’s incomes list will display the updated incomes list.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. Command is invalid.
+
+ * 1a1. Fine$$e shows an error message.
+
+ Use case ends.
+
+* 1b. Bookmark incomes list is empty.
+
+ * 1b1. Fine$$e shows an error message.
+
+ Use case ends.
+
+* 1c. Index given is invalid.
+
+ * 1c1. Fine$$e shows an error message.
+
+ Use case ends.
+
+--------------------------------------------------------------------------------------------------------------------
+
+## **Appendix D: Non-Functional Requirements**
+
+1. Should work on any mainstream OS as long as it has Java `11` or above installed.
+2. Should be able to hold up to 1000 expenses/incomes without a noticeable sluggishness in performance for typical usage.
+3. 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.
+4. The data should be stored locally and should be in a human-editable text file.
+5. The data should not be stored in a Database Management System (DBMS).
+6. Should not require an installer.
+7. Should not depend on any remote server.
+
+--------------------------------------------------------------------------------------------------------------------
+
+## **Appendix E: Glossary**
+
+* **Expense**: A single transaction that results in a decrease in cash
+* **Income**: A single transaction that results in an increase in cash
+* **Mainstream OS**: Windows, Linux, Unix, OS-X
+* **Savings**: Net gain (positive) or loss (negative) in cash over a set period of time
+* **Data Fields**: The title, amount, date, and categories that make up an Expense or Income.
+
+--------------------------------------------------------------------------------------------------------------------
+
+## **Appendix F: Instructions for manual testing**
+
+Given below are instructions to test the app manually.
+
+
:information_source: **Note:** These instructions only provide a starting point for testers to work on;
+testers are expected to do more *exploratory* testing.
+
+
+
+### Launch and shutdown
+
+1. Initial launch
+
+ 1. Download the jar file and copy into an empty folder.
+
+ 1. Double-click the jar file.
+ Expected: Shows the GUI with a set of sample data. The window size may not be optimum.
+
+1. Saving window preferences
+
+ 1. Resize the window to an optimum size. Move the window to a different location. Close the window.
+
+ 1. Re-launch the app by double-clicking the jar file.
+ Expected: The most recent window size and location is retained.
+
+### Help and switching tabs
+
+1. View help
+
+ 1. Test case: `help`
+ Expected: UI switches to the user guide.
+
+ 1. Test case: `help me`
+ Expected: There is no change to the UI. An error message is shown as the command cannot have any arguments.
+
+1. Switching tabs
+
+ 1. Test case: `tab 2`
+ Expected: UI switches to the Expenses tab.
+
+ 1. Test case: `tab overview`
+ Expected: UI does not switch tabs. An error message is shown as the command format is invalid.
+
+ 1. Test case: `tab 5`
+ Expected: UI does not switch tabs. An error message is shown as the specified tab does not exist.
+
+### Transactions
+
+1. Adding an expense
+
+ 1. Test case: `add-expense t/Bubble Tea a/5 d/03/10/2020 c/Food & Beverage`
+ Expected: An expense titled `Bubble Tea` is added with the given details. UI switches to the Expenses tab.
+
+ 1. Test case: `add-expense t/Bubble Tea a/5 c/Food c/Beverage`
+ Expected: An expense titled `Bubble Tea` is added with the current date and two categories. UI switches to the Expenses tab.
+
+ 1. Test case: `add-expense t/Bubble Tea`
+ Expected: No expense is added. An error message is shown as the command format is invalid.
+
+ 1. Test case: `add-expense t/Bubble Tea a/5.000 d/03/10/2020 c/Food & Beverage`
+ Expected: No expense is added. An error message is shown as the parameter format is wrong.
+
+ 1. Test case: `add-expense t/ a/5.00 d/03/10/2020 c/Food & Beverage`
+ Expected: No expense is added. An error message is shown as the parameter is empty.
+
+1. Adding an income
+
+ 1. Test case: `add-income t/Internship a/560 d/03/10/2020 c/Work`
+ Expected: An income titled `Internship` is added with the given details. UI switches to the Incomes tab.
+
+ 1. Test case: `add-income t/Internship a/560 c/Work c/Internship`
+ Expected: An expense titled `Internship` is added with the current date and two categories. UI switches to the Incomes tab.
+
+ 1. Test case: `add-income t/Internship`
+ Expected: No expense is added. An error message is shown as the command format is invalid.
+
+ 1. Test case: `add-income t/Internship a/560 d/03-10-2020 c/Work`
+ Expected: No expense is added. An error message is shown as the parameter format is wrong.
+
+ 1. Test case: `add-income t/Internship a/ d/03/10/2020 c/Work`
+ Expected: No expense is added. An error message is shown as the parameter is empty.
+
+1. Editing a transaction
+
+ 1. Prerequisite: UI is on Expenses or Incomes tab. List displayed contains less than 5 transactions.
+
+ 1. Test case: `edit 1 t/Taxi a/10 d/31/10/2020 c/Transport`
+ Expected: First transaction in the list is edited with all details changed.
+
+ 1. Test case: `edit 1 c/`
+ Expected: First transaction in the list is edited with all categories cleared.
+
+ 1. Test case: `edit t/Taxi`
+ Expected: No transaction is edited. An error message is shown as the command format is invalid.
+
+ 1. Test case: `edit 5 a/10`
+ Expected: No transaction is edited. An error message is shown as the index provided is invalid.
+
+ 1. Test case: `edit 1 d/10/31/2020`
+ Expected: No transaction is edited. An error message is shown as the parameter format is wrong.
+
+ 1. Test case: `edit 1 d/`
+ Expected: No transaction is edited. An error message is shown as the parameter is empty.
+
+ 1. Test case: `edit 1`
+ Expected: No transaction is edited. An error message is shown as at least one parameter must be supplied.
+
+1. Deleting a transaction
+
+ 1. Prerequisite: UI is on Expenses or Incomes tab. List displayed contains less than 5 transactions.
+
+ 1. Test case: `delete 1`
+ Expected: First transaction in the list is deleted.
+
+ 1. Test case: `delete first`
+ Expected: No transaction is deleted. An error message is shown as the command format is invalid.
+
+ 1. Test case: `delete 5`
+ Expected: No transaction is deleted. An error message is shown as the index provided is invalid.
+
+1. Listing transactions
+
+ 1. Test case: `ls-expense`
+ Expected: UI switches to Expenses tab. All expenses are listed.
+
+ 1. Test case: `ls-income`
+ Expected: UI switches to Incomes tab. All incomes are listed.
+
+ 1. Test case: `ls-income all`
+ Expected: There is no change in the UI. An error message is shown as the command cannot have any arguments.
+
+1. Finding transactions
+
+ 1. Prerequisite: UI is on Overview, Expenses or Incomes tab.
+
+ 1. Test case: `find t/Bubble Tea`
+ Expected: All transactions in the displayed list containing the keyphrase `Bubble Tea` (case-insensitive) are displayed.
+
+ 1. Test case: `find af/5 c/Food`
+ Expected: All transactions in the displayed list with the category `Food` and amount greater than or equal to `$5.00` are displayed.
+
+ 1. Test case: `find df/01/09/2020 dt/30/09/2020`
+ Expected: All transactions in the displayed list in September 2020 are displayed.
+
+ 1. Test case: `find tea`
+ Expected: There is no change in the UI. An error message is shown as the command format is wrong.
+
+ 1. Test case: `find d/`
+ Expected: There is no change in the UI. An error message is shown as the parameter is empty.
+
+ 1. Test case: `find af/10 at/5`
+ Expected: There is no change in the UI. An error message is shown as the range provided is invalid.
+
+### Budgeting
+
+1. Setting expense limit
+
+ 1. Test case: `set-expense-limit a/500`
+ Expected: UI switches to Overview tab. Monthly expense limit is changed to `$500.00`. Remaining monthly budget is recalculated.
+
+ 1. Test case: `set-expense-limit 500`
+ Expected: Monthly expense limit remains unchanged. An error message is shown as the command format is invalid.
+
+ 1. Test case: `set-expense-limit a/`
+ Expected: Monthly expense limit remains unchanged. An error message is shown as the parameter cannot be empty.
+
+1. Setting savings goal
+
+ 1. Test case: `set-savings-goal a/500`
+ Expected: UI switches to Overview tab. Monthly savings goal is changed to `$500.00`.
+
+ 1. Test case: `set-savings-goal 500`
+ Expected: Monthly savings goal remains unchanged. An error message is shown as the command format is invalid.
+
+ 1. Test case: `set-savings-goal a/`
+ Expected: Monthly savings goal remains unchanged. An error message is shown as the parameter cannot be empty.
+
+### Bookmark transactions
+
+1. Adding a bookmark expense
+
+ 1. Test case: `add-bookmark-expense t/Phone Bill a/24 c/Utilities`
+ Expected: A bookmark expense titled `Phone Bill` is added with the given details. UI switches to the Expenses tab.
+
+ 1. Test case: `add-bookmark-expense t/Phone Bill`
+ Expected: No bookmark expense is added. An error message is shown as the command format is invalid.
+
+ 1. Test case: `add-bookmark-expense t/Phone Bill a/24.0 c/Utilities`
+ Expected: No bookmark expense is added. An error message is shown as the parameter format is wrong.
+
+ 1. Test case: `add-bookmark-expense t/ a/24 c/Utilities`
+ Expected: No bookmark expense is added. An error message is shown as the parameter is empty.
+
+ 1. Test case: `add-bookmark-expense t/Phone Bill a/24 d/10/10/2020 c/Utilities`
+ Expected: No bookmark expense is added. An error message is shown as bookmark expenses should not contain dates.
+
+ 1. Test case: `add-bookmark-expense t/Phone Bill a/24 c/Utilities` (again)
+ Expected: No bookmark expense is added. An error message is shown as duplicate bookmark expenses cannot be added.
+
+1. Adding a bookmark income
+
+ 1. Test case: `add-bookmark-income t/Summer Internship a/1000 c/Work`
+ Expected: A bookmark income titled `Summer Internship` is added with the given details. UI switches to the Income tab.
+
+ 1. Test case: `add-bookmark-income a/1000 c/Work`
+ Expected: No bookmark income is added. An error message is shown as the command format is invalid.
+
+ 1. Test case: `add-bookmark-income t/Summer Internship a/1000.000 c/Work`
+ Expected: No bookmark income is added. An error message is shown as the parameter format is wrong.
+
+ 1. Test case: `add-bookmark-income t/Summer Internship a/ c/Work`
+ Expected: No bookmark income is added. An error message is shown as the parameter is empty.
+
+ 1. Test case: `add-bookmark-income t/Summer Internship a/1000 d/10/10/2020 c/Work`
+ Expected: No bookmark income is added. An error message is shown as bookmark incomes should not contain dates.
+
+ 1. Test case: `add-bookmark-income t/Summer Internship a/1000 c/Work` (again)
+ Expected: No bookmark income is added. An error message is shown as duplicate bookmark incomes cannot be added.
+
+1. Editing a bookmark transaction
+
+ 1. Prerequisite: UI is on Expenses or Incomes tab. List displayed contains less than 5 bookmark transactions.
+
+ 1. Test case: `edit-bookmark 1 t/Taxi a/10 c/Transport`
+ Expected: First bookmark transaction in the list is edited with all details changed.
+
+ 1. Test case: `edit-bookmark 1 c/`
+ Expected: First bookmark transaction in the list is edited with all categories cleared.
+
+ 1. Test case: `edit-bookmark t/Taxi`
+ Expected: No bookmark transaction is edited. An error message is shown as the command format is invalid.
+
+ 1. Test case: `edit-bookmark 5 a/10`
+ Expected: No bookmark transaction is edited. An error message is shown as the index provided is invalid.
+
+ 1. Test case: `edit-bookmark 1 a/12.345`
+ Expected: No bookmark transaction is edited. An error message is shown as the parameter format is wrong.
+
+ 1. Test case: `edit-bookmark 1 d/`
+ Expected: No bookmark transaction is edited. An error message is shown as the parameter is empty.
+
+ 1. Test case: `edit 1`
+ Expected: No bookmark transaction is edited. An error message is shown as at least one parameter must be supplied.
+
+1. Deleting a bookmark transaction
+
+ 1. Prerequisite: UI is on Expenses or Incomes tab. List displayed contains less than 5 bookmark transactions.
+
+ 1. Test case: `delete-bookmark 1`
+ Expected: First bookmark transaction in the list is deleted.
+
+ 1. Test case: `delete-bookmark first`
+ Expected: No bookmark transaction is deleted. An error message is shown as the command format is invalid.
+
+ 1. Test case: `delete-bookmark 5`
+ Expected: No bookmark transaction is deleted. An error message is shown as the index provided is invalid.
+
+1. Converting a bookmark transaction
+
+ 1. Prerequisite: UI is on Expenses or Incomes tab. List displayed contains less than 5 bookmark transactions.
+
+ 1. Test case: `convert-bookmark 1 d/10/10/2020`
+ Expected: First bookmark transaction in the list is converted to a transaction with the given date.
+
+ 1. Test case: `convert-bookmark 1 c/`
+ Expected: First bookmark transaction in the list is converted to a transaction with the current date.
+
+ 1. Test case: `convert-bookmark d/10/10/2020`
+ Expected: No bookmark transaction is converted. An error message is shown as the command format is invalid.
+
+ 1. Test case: `convert-bookmark 5 d/10/10/2020`
+ Expected: No bookmark transaction is converted. An error message is shown as the index provided is invalid.
+
+ 1. Test case: `convert-bookmark 1 d/10/31/2020`
+ Expected: No bookmark transaction is converted. An error message is shown as the parameter format is wrong.
+
+ 1. Test case: `convert-bookmark 1 d/`
+ Expected: No bookmark transaction is converted. An error message is shown as the parameter is empty.
+
+### Saving data
+
+1. Dealing with missing data files
+
+ 1. Delete the file `fine$$e.json` in the `data` folder.
+
+ 1. Launch the app by double-clicking the jar file.
+ Expected: Shows the GUI with a set of sample data.
+
+1. Dealing with corrupted data files
+
+ 1. Replace the contents of `fine$$e.json` in the `data` folder with the text `File Corrupted`.
+
+ 1. Launch the app by double-clicking the jar file.
+ Expected: Shows the GUI with no data.
+
+1. Deleting data file while app is running
+
+ 1. Launch the app by double-clicking the jar file.
-1. Dealing with missing/corrupted data files
+ 1. While the app is running, delete the file `fine$$e.json` in the `data` folder.
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
+ 1. Close the app.
+ Expected: `fine$$e.json` is regenerated with the data from the app.
-1. _{ more test cases … }_
diff --git a/docs/Documentation.md b/docs/Documentation.md
deleted file mode 100644
index 77c5ba73f66..00000000000
--- a/docs/Documentation.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
-layout: page
-title: Documentation guide
----
-
-**Setting up and maintaining the project website:**
-
-* We use [**Jekyll**](https://jekyllrb.com/) to manage documentation.
-* The `docs/` folder is used for documentation.
-* To learn how set it up and maintain the project website, follow the guide [_[se-edu/guides] **Using Jekyll for project documentation**_](https://se-education.org/guides/tutorials/jekyll.html).
-
-
-**Style guidance:**
-
-* Follow the [**_Google developer documentation style guide_**](https://developers.google.com/style).
-
-* Also relevant is the [_[se-edu/guides] **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html)
-
-**Diagrams:**
-
-* See the [_[se-edu/guides] **Using PlantUML**_](https://se-education.org/guides/tutorials/plantUml.html)
-
-**Converting a document to the PDF format:**
-
-* See the guide [_[se-edu/guides] **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html)
diff --git a/docs/GanttChart.md b/docs/GanttChart.md
new file mode 100644
index 00000000000..2461964b42d
--- /dev/null
+++ b/docs/GanttChart.md
@@ -0,0 +1,7 @@
+---
+layout: chart
+title: Gantt Chart
+---
+
+
diff --git a/docs/Gemfile b/docs/Gemfile
index 999a7099d8d..696877ea7c5 100644
--- a/docs/Gemfile
+++ b/docs/Gemfile
@@ -5,5 +5,15 @@ source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
gem 'jekyll'
-gem 'github-pages', group: :jekyll_plugins
+gem 'minima'
+gem 'jekyll-coffeescript'
+gem 'jekyll-default-layout'
+gem 'jekyll-gist'
+gem 'jekyll-github-metadata'
+gem 'jekyll-optional-front-matter'
+gem 'jekyll-paginate'
+gem 'jekyll-readme-index'
+gem 'jekyll-titles-from-headings'
+gem 'jekyll-relative-links'
gem 'wdm', '~> 0.1.0' if Gem.win_platform?
+gem 'jekyll-spaceship', git: 'https://github.com/AY2021S1-CS2103T-W16-3/jekyll-spaceship.git'
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
index 4caff5b900c..ae25549aa28 100644
--- a/docs/Gemfile.lock
+++ b/docs/Gemfile.lock
@@ -1,88 +1,34 @@
+GIT
+ remote: https://github.com/AY2021S1-CS2103T-W16-3/jekyll-spaceship.git
+ revision: 710f3039aa9a7f344079cb9bd9532778805eb7b8
+ specs:
+ jekyll-spaceship (0.9.2)
+ gemoji (~> 4.0.0.rc2)
+ jekyll (>= 3.6, < 5.0)
+ nokogiri (~> 1.6)
+ rainbow (~> 3.0)
+
GEM
remote: https://rubygems.org/
specs:
- activesupport (6.0.3.1)
- concurrent-ruby (~> 1.0, >= 1.0.2)
- i18n (>= 0.7, < 2)
- minitest (~> 5.1)
- tzinfo (~> 1.1)
- zeitwerk (~> 2.2, >= 2.2.2)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
coffee-script (2.4.1)
coffee-script-source
execjs
- coffee-script-source (1.11.1)
+ coffee-script-source (1.12.2)
colorator (1.1.0)
- commonmarker (0.17.13)
- ruby-enum (~> 0.5)
concurrent-ruby (1.1.6)
- dnsruby (1.61.3)
- addressable (~> 2.5)
em-websocket (0.5.1)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0)
- ethon (0.12.0)
- ffi (>= 1.3.0)
eventmachine (1.2.7-x64-mingw32)
execjs (2.7.0)
faraday (1.0.1)
multipart-post (>= 1.2, < 3)
ffi (1.12.2-x64-mingw32)
forwardable-extended (2.6.0)
- gemoji (3.0.1)
- github-pages (204)
- github-pages-health-check (= 1.16.1)
- jekyll (= 3.8.5)
- jekyll-avatar (= 0.7.0)
- jekyll-coffeescript (= 1.1.1)
- jekyll-commonmark-ghpages (= 0.1.6)
- jekyll-default-layout (= 0.1.4)
- jekyll-feed (= 0.13.0)
- jekyll-gist (= 1.5.0)
- jekyll-github-metadata (= 2.13.0)
- jekyll-mentions (= 1.5.1)
- jekyll-optional-front-matter (= 0.3.2)
- jekyll-paginate (= 1.1.0)
- jekyll-readme-index (= 0.3.0)
- jekyll-redirect-from (= 0.15.0)
- jekyll-relative-links (= 0.6.1)
- jekyll-remote-theme (= 0.4.1)
- jekyll-sass-converter (= 1.5.2)
- jekyll-seo-tag (= 2.6.1)
- jekyll-sitemap (= 1.4.0)
- jekyll-swiss (= 1.0.0)
- jekyll-theme-architect (= 0.1.1)
- jekyll-theme-cayman (= 0.1.1)
- jekyll-theme-dinky (= 0.1.1)
- jekyll-theme-hacker (= 0.1.1)
- jekyll-theme-leap-day (= 0.1.1)
- jekyll-theme-merlot (= 0.1.1)
- jekyll-theme-midnight (= 0.1.1)
- jekyll-theme-minimal (= 0.1.1)
- jekyll-theme-modernist (= 0.1.1)
- jekyll-theme-primer (= 0.5.4)
- jekyll-theme-slate (= 0.1.1)
- jekyll-theme-tactile (= 0.1.1)
- jekyll-theme-time-machine (= 0.1.1)
- jekyll-titles-from-headings (= 0.5.3)
- jemoji (= 0.11.1)
- kramdown (= 1.17.0)
- liquid (= 4.0.3)
- mercenary (~> 0.3)
- minima (= 2.5.1)
- nokogiri (>= 1.10.4, < 2.0)
- rouge (= 3.13.0)
- terminal-table (~> 1.4)
- github-pages-health-check (1.16.1)
- addressable (~> 2.3)
- dnsruby (~> 1.60)
- octokit (~> 4.0)
- public_suffix (~> 3.0)
- typhoeus (~> 1.3)
- html-pipeline (2.12.3)
- activesupport (>= 2)
- nokogiri (>= 1.4)
+ gemoji (4.0.0.rc2)
http_parser.rb (0.6.0)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
@@ -99,18 +45,9 @@ GEM
pathutil (~> 0.9)
rouge (>= 1.7, < 4)
safe_yaml (~> 1.0)
- jekyll-avatar (0.7.0)
- jekyll (>= 3.0, < 5.0)
- jekyll-coffeescript (1.1.1)
+ jekyll-coffeescript (2.0.0)
coffee-script (~> 2.2)
- coffee-script-source (~> 1.11.1)
- jekyll-commonmark (1.3.1)
- commonmarker (~> 0.14)
- jekyll (>= 3.7, < 5.0)
- jekyll-commonmark-ghpages (0.1.6)
- commonmarker (~> 0.17.6)
- jekyll-commonmark (~> 1.2)
- rouge (>= 2.0, < 4.0)
+ coffee-script-source (~> 1.12)
jekyll-default-layout (0.1.4)
jekyll (~> 3.0)
jekyll-feed (0.13.0)
@@ -120,77 +57,21 @@ GEM
jekyll-github-metadata (2.13.0)
jekyll (>= 3.4, < 5.0)
octokit (~> 4.0, != 4.4.0)
- jekyll-mentions (1.5.1)
- html-pipeline (~> 2.3)
- jekyll (>= 3.7, < 5.0)
jekyll-optional-front-matter (0.3.2)
jekyll (>= 3.0, < 5.0)
jekyll-paginate (1.1.0)
jekyll-readme-index (0.3.0)
jekyll (>= 3.0, < 5.0)
- jekyll-redirect-from (0.15.0)
- jekyll (>= 3.3, < 5.0)
jekyll-relative-links (0.6.1)
jekyll (>= 3.3, < 5.0)
- jekyll-remote-theme (0.4.1)
- addressable (~> 2.0)
- jekyll (>= 3.5, < 5.0)
- rubyzip (>= 1.3.0)
jekyll-sass-converter (1.5.2)
sass (~> 3.4)
jekyll-seo-tag (2.6.1)
jekyll (>= 3.3, < 5.0)
- jekyll-sitemap (1.4.0)
- jekyll (>= 3.7, < 5.0)
- jekyll-swiss (1.0.0)
- jekyll-theme-architect (0.1.1)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-cayman (0.1.1)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-dinky (0.1.1)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-hacker (0.1.1)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-leap-day (0.1.1)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-merlot (0.1.1)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-midnight (0.1.1)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-minimal (0.1.1)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-modernist (0.1.1)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-primer (0.5.4)
- jekyll (> 3.5, < 5.0)
- jekyll-github-metadata (~> 2.9)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-slate (0.1.1)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-tactile (0.1.1)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-time-machine (0.1.1)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
jekyll-titles-from-headings (0.5.3)
jekyll (>= 3.3, < 5.0)
jekyll-watch (2.2.1)
listen (~> 3.0)
- jemoji (0.11.1)
- gemoji (~> 3.0)
- html-pipeline (~> 2.2)
- jekyll (>= 3.0, < 5.0)
kramdown (1.17.0)
liquid (4.0.3)
listen (3.2.1)
@@ -202,9 +83,8 @@ GEM
jekyll (>= 3.5, < 5.0)
jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1)
- minitest (5.14.1)
multipart-post (2.1.1)
- nokogiri (1.10.9-x64-mingw32)
+ nokogiri (1.10.10-x64-mingw32)
mini_portile2 (~> 2.4.0)
octokit (4.18.0)
faraday (>= 0.9)
@@ -212,13 +92,11 @@ GEM
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (3.1.1)
+ rainbow (3.0.0)
rb-fsevent (0.10.4)
rb-inotify (0.10.1)
ffi (~> 1.0)
rouge (3.13.0)
- ruby-enum (0.8.0)
- i18n
- rubyzip (2.3.0)
safe_yaml (1.0.5)
sass (3.7.4)
sass-listen (~> 4.0.0)
@@ -228,23 +106,24 @@ GEM
sawyer (0.8.2)
addressable (>= 2.3.5)
faraday (> 0.8, < 2.0)
- terminal-table (1.8.0)
- unicode-display_width (~> 1.1, >= 1.1.1)
- thread_safe (0.3.6)
- typhoeus (1.4.0)
- ethon (>= 0.9.0)
- tzinfo (1.2.7)
- thread_safe (~> 0.1)
- unicode-display_width (1.7.0)
wdm (0.1.1)
- zeitwerk (2.3.0)
PLATFORMS
x64-mingw32
DEPENDENCIES
- github-pages
jekyll
+ jekyll-coffeescript
+ jekyll-default-layout
+ jekyll-gist
+ jekyll-github-metadata
+ jekyll-optional-front-matter
+ jekyll-paginate
+ jekyll-readme-index
+ jekyll-relative-links
+ jekyll-spaceship!
+ jekyll-titles-from-headings
+ minima
wdm (~> 0.1.0)
BUNDLED WITH
diff --git a/docs/Logging.md b/docs/Logging.md
deleted file mode 100644
index 5e4fb9bc217..00000000000
--- a/docs/Logging.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-layout: page
-title: Logging guide
----
-
-* We are using `java.util.logging` package for logging.
-* The `LogsCenter` class is used to manage the logging levels and logging destinations.
-* The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level.
-* Log messages are output through the console and to a `.log` file.
-* The output logging level can be controlled using the `logLevel` setting in the configuration file (See the [Configuration guide](Configuration.md) section).
-* **When choosing a level for a log message**, follow the conventions given in [_[se-edu/guides] Java: Logging conventions_](https://se-education.org/guides/conventions/java/logging.html).
diff --git a/docs/Minutes.md b/docs/Minutes.md
new file mode 100644
index 00000000000..6a8d4a3e867
--- /dev/null
+++ b/docs/Minutes.md
@@ -0,0 +1,15 @@
+---
+layout: page
+title: Meeting Minutes
+---
+
+
+ {% assign sorted_minutes = site.pages | sort: "date" | reverse %}
+ {% for page in sorted_minutes %}
+ {% if page.layout == "minutes" %}
+
diff --git a/docs/NoExternalSite.html b/docs/NoExternalSite.html
new file mode 100644
index 00000000000..393ada70906
--- /dev/null
+++ b/docs/NoExternalSite.html
@@ -0,0 +1,13 @@
+---
+layout: widescreen
+title: No External Site
+---
+
+
+
+ Not allowed to navigate to external sites.
+
+
+ Click on any link in the header to navigate back.
+
+
diff --git a/docs/SettingUp.md b/docs/SettingUp.md
deleted file mode 100644
index b89b691fed9..00000000000
--- a/docs/SettingUp.md
+++ /dev/null
@@ -1,55 +0,0 @@
----
-layout: page
-title: Setting up and getting started
----
-
-* Table of Contents
-{:toc}
-
-
---------------------------------------------------------------------------------------------------------------------
-
-## Setting up the project in your computer
-
-
:exclamation: **Caution:**
-
-Follow the steps in the following guide precisely. Things will not work out if you deviate in some steps.
-
-
-First, **fork** this repo, and **clone** the fork into your computer.
-
-If you plan to use Intellij IDEA (highly recommended):
-1. **Configure the JDK**: Follow the guide [_[se-edu/guides] IDEA: Configuring the JDK_](https://se-education.org/guides/tutorials/intellijJdk.html) to to ensure Intellij is configured to use **JDK 11**.
-1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
- :exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project.
-1. **Verify the setup**:
- 1. Run the `seedu.address.Main` and try a few commands.
- 1. [Run the tests](Testing.md) to ensure they all pass.
-
---------------------------------------------------------------------------------------------------------------------
-
-## Before writing code
-
-1. **Configure the coding style**
-
- If using IDEA, follow the guide [_[se-edu/guides] IDEA: Configuring the code style_](https://se-education.org/guides/tutorials/checkstyle.html) to set up IDEA's coding style to match ours.
-
-
:bulb: **Tip:**
-
- Optionally, you can follow the guide [_[se-edu/guides] Using Checkstyle_](https://se-education.org/guides/tutorials/checkstyle.html) to find how to use the CheckStyle within IDEA e.g., to report problems _as_ you write code.
-
-
-1. **Set up CI**
-
- This project comes with a GitHub Actions config files (in `.github/workflows` folder). When GitHub detects those files, it will run the CI for your project automatically at each push to the `master` branch or to any PR. No set up required.
-
-1. **Learn the design**
-
- When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [AddressBook’s architecture](DeveloperGuide.md#architecture).
-
-1. **Do the tutorials**
- These tutorials will help you get acquainted with the codebase.
-
- * [Tracing code](tutorials/TracingCode.md)
- * [Removing fields](tutorials/RemovingFields.md)
- * [Adding a new command](tutorials/AddRemark.md)
diff --git a/docs/Testing.md b/docs/Testing.md
deleted file mode 100644
index 8a99e82438a..00000000000
--- a/docs/Testing.md
+++ /dev/null
@@ -1,36 +0,0 @@
----
-layout: page
-title: Testing guide
----
-
-* Table of Contents
-{:toc}
-
---------------------------------------------------------------------------------------------------------------------
-
-## Running tests
-
-There are two ways to run tests.
-
-* **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 test` (Mac/Linux: `./gradlew clean test`)
-
-
:link: **Link**: Read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html) to learn more about using Gradle.
-
-
---------------------------------------------------------------------------------------------------------------------
-
-## Types of tests
-
-This project has three types of tests:
-
-1. *Unit tests* targeting the lowest level methods/classes.
- e.g. `seedu.address.commons.StringUtilTest`
-1. *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`
-1. 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`
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index b91c3bab04d..7a169dfe04f 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -3,176 +3,1084 @@ layout: page
title: User Guide
---
-AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps.
+
+ * Table of Contents
+ {:toc}
+
+
+--------------------------------------------------------------------------------------------------------------------
+## 1. Introduction
+
+### 1.1 About Fine$$e
+
+Welcome to Fine$$e - your personal finance tracker!
+
+Looking for an all-in-one solution to help you develop good financial habits? Look no further!
+
+Fine\\$\\$e is an integrated platform fully customized for tertiary (including university) students with the aim of helping you to track your finances effectively.
+Fine\\$\\$e allows you to keep track of your incomes, expenses and savings with a few simple commands.
+Furthermore, to help you cultivate good financial habits, Fine$$e allows you to budget your finances by setting an expense limit and savings goal, as well as viewing your past spending and saving trends.
+
+Commands can be input easily via the Command Line Interface (CLI), and the information you provide Fine$$e with will be displayed on our sleek Graphical User Interface (GUI).
+
+If you wish to better manage your finances while cultivating good financial habits, then Fine\$\$e is definitely the application for you!
-* Table of Contents
-{:toc}
+Explore our User Guide to find out more about Fine\$\$e’s amazing features.
+
+### 1.2 Navigating the User Guide
+
+The aim of the User Guide is to provide you with all the necessary information required for you to fully utilize Fine$$e.
+We have ensured that the information provided is concise, accessible and easily readable.
+
+Head to [Section 2. "Quick Start"](#2-quick-start) for help on setting up.
+
+[Section 3. "Overview of Features"](#3-overview-of-features) provides a brief overview of Fine\$\$e's myriad of features, while [Section 4. "Features"](#4-features) contains an in-depth breakdown of all the features that Fine\$\$e has to offer.
+
+If you have forgotten the input format for a particular command, fret not as [Section 5. "Command Summary"](#5-command-summary) contains a list of all Fine$$e's command input formats.
+
+A list of the terms used in this user guide as well as their definitions is available in [Section 6. Glossary](#6-glossary).
+
+If you have any questions regarding Fine$$e, do check out [Section 7. FAQ](#7-faq).
+
+
+
+Do take note of the following symbols and formatting used throughout this document:
+
+`Code in code blocks are for user input, Fine$$e data values, or file names.`
+
+:bulb: This is used to indicate useful tips and tricks.
+
+:information_source: This indicates useful information.
+
+:warning: This indicates important information.
+
+
--------------------------------------------------------------------------------------------------------------------
+## 2. Quick start
+
+This section contains a step-by-step guide on how to install Fine\\$\\$e and get it to work on your computer.
+It also shows the various components that make up Fine\\$\\$e's user interface.
+
+Let's get started!
-## Quick start
+### 2.1 Installation
-1. Ensure you have Java `11` or above installed in your Computer.
+1. Ensure you have Java 11 or above installed in your computer.
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
+1. Download the latest version of `finesse.jar` from [here](https://github.com/AY2021S1-CS2103T-W16-3/tp/releases).
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+1. Copy the file to the folder you want to use as the *home folder* for Fine$$e.
+
+1. Double-click the file to start the app.
+The Fine$$e GUI should appear in a few seconds, with sample data included, similar to the below image.
-1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
![Ui](images/Ui.png)
-1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
+1. Type a command in the command box and press Enter to execute it.
+ e.g. typing `help` and pressing Enter will bring up this user guide in the finance tracker.
Some example commands you can try:
- * **`list`** : Lists all contacts.
+ * `list`: Lists all transactions in the current list.
+
+ * `add-expense t/Bubble Tea a/5 d/03/10/2020 c/Food & Beverage`:
+ Adds an expense with the title `Bubble Tea`, amount `$5.00`, date `03/10/2020` and category `Food & Beverage` to the finance tracker. The UI switches to the [Expenses tab](#223-expenses-tab).
+
+ * `tab 3`: Switches to the [Expenses tab](#223-expenses-tab).
+
+ * `delete 3`: Deletes the 3rd transaction shown in the current list.
+ Note that this command only works in the [Expenses tab](#223-expenses-tab) and the [Incomes tab](#222-incomes-tab).
+
+ * `exit`: Exits the app.
+
+1. Once you are comfortable with the various features in Fine\\$\\$e, [clear the sample data](#410-clear-data-clear) by typing `clear`, pressing Enter,
+typing `clear` again, and pressing Enter again.
+
+1. Start tracking your own finances with Fine\\$\\$e!
+
+### 2.2 Layout of Fine$$e's Interface
- * **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book.
+The user interface of Fine$$e is divided into 4 tabs, each serving a specific purpose.
- * **`delete`**`3` : Deletes the 3rd contact shown in the current list.
+#### 2.2.1 Overview Tab
- * **`clear`** : Deletes all contacts.
+Displays a list of all recent transactions (both [incomes](#44-income) and [expenses](#43-expense)), along with your [expense limit](#47-expense-limit), [savings goal](#48-savings-goal), remaining budget and current savings.
- * **`exit`** : Exits the app.
+> :information_source: Transaction amounts are prepended with either `+` or `-` so as to distinguish between [incomes](#44-income) and [expenses](#43-expense) respectively.
+> In the case of the latter, the amount is also highlighted in red.
+> This applies only to the Overview tab.
-1. Refer to the [Features](#features) below for details of each command.
+![Overview Tab](images/userguide/OverviewTab.png)
+
+#### 2.2.2 Incomes Tab
+
+Displays a list of [incomes](#44-income) and [bookmark incomes](#46-bookmark-income).
+
+![Incomes Tab](images/userguide/IncomesTab.png)
+
+#### 2.2.3 Expenses Tab
+
+Displays a list of [expenses](#43-expense) and [bookmark expenses](#45-bookmark-expense).
+
+![Expenses Tab](images/userguide/ExpensesTab.png)
+
+#### 2.2.4 Analytics Tab
+
+Displays chart visualizations of your monthly expenses, incomes and savings.
+
+![Analytics Tab](images/userguide/AnalyticsTab.png)
--------------------------------------------------------------------------------------------------------------------
-## Features
+## 3. Overview of Features
+
+This section will provide you with a brief overview of Fine\\$\\$e, so that you may get a better sense of Fine\\$\\$e’s basic functionalities before diving into its specific commands.
+1. Manage your incomes and expenses easily
+ * Track incomes/expenses by entering their details e.g title, amount, date
+ * Categorize your incomes and expenses to better organize your finances
+ * Search for incomes/expenses easily by keywords
+1. Add bookmark transactions
+ * Create templates for transactions that occur regularly so that frequent expenses/incomes can be added to the finance tracker more conveniently
+1. Budgeting
+ * Set a monthly expense limit to track your remaining budget for the month
+ * Set a monthly savings goal to help you cultivate good saving habits
+1. Analytics
+ * View trends in your finances to analyze your spending and saving habits and better plan for the future
+
+--------------------------------------------------------------------------------------------------------------------
+
+## 4. Features
+
+This section aims to provide you with in-depth details of Fine\$\$e's unique features, with relevant examples.
-**:information_source: Notes about the command format:**
+**:information_source: Notes about the command format:**
* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`.
+ e.g. in `add-expense t/TITLE`, `TITLE` is a parameter which can be used as `add t/Bubble Tea`.
-* Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`.
+* Items in square brackets (`[]`) are optional.
+ e.g `t/TITLE [c/CATEGORY]` can be used as `t/Bubble Tea c/Food & Beverage` or as `t/Bubble Tea`.
-* Items with `…` after them can be used multiple times including zero times.
- e.g. `[t/TAG]…` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc.
+* Items in square brackets, with `...` after the parameter, can be used multiple times (including zero times).
+ e.g. `[c/CATEGORY...]` can be used as `c/Food & Beverage`, `c/Food & Beverage c/Tea` etc, or even omitted completely.
* Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
+ e.g. if the command specifies `t/TITLE a/AMOUNT`, `a/AMOUNT t/TITLE` is also acceptable.
+
+* Any leading/trailing whitespaces for the parameters or the user input as a whole will be ignored.
+ e.g. ` find t/Bubble Tea`, `find t/ Bubble Tea` and `find t/Bubble Tea ` will be treated as `find t/Bubble Tea`.
-### Viewing help : `help`
+
+
+**:warning: IMPORTANT :warning:**
+
+The formats of the parameters used in the rest of the document are as follows:
+* `TITLE`, `TITLE_KEYPHRASE` and `CATEGORY` should consist of printable ASCII characters.
+ * The set of printable ASCII characters consists of alphanumeric characters, space, and several special characters.
+ Below is an exhaustive list of printable ASCII characters (excluding line breaks):
+ ```
+ 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijkl
+ mnopqrstuvwxyz !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
+ ```
+* `AMOUNT`, `AMOUNT_FROM` and `AMOUNT_TO` should each be a non-negative number with up to 8 digits before the decimal point and with 0 or 2 decimal places.
+ An optional `$` may be included in front as well.
+* `DATE`, `DATE_FROM` and `DATE_TO` should each be a valid calendar date in `dd/mm/yyyy` format, representing day, month, and year respectively.
+ * Valid ranges for days and months are governed by the rules of the [Gregorian Calendar](https://en.wikipedia.org/wiki/Gregorian_calendar#Description).
+ * Months should only range from 1 to 12 (inclusive), representing the 12 months in a year.
+ * Days should only range from 1 to 28, 29, 30, or 31 (all inclusive),
+ depending on the number of days in the given month and whether the given year is a leap year.
+ * For day and month values, single-digit values must be padded with leading zeroes, meaning that a value of 5 should be written as `05` instead of `5`.
+ * For year values, it must be written in the standard 4-digit format, so 2019 should be written as `2019` and not just `19`.
+ * Dates cannot be earlier than 1 January 1970 (`01/01/1970`).
+ * Dates cannot be later than the current date.
+* `INDEX` should be a positive integer.
+
+Unless stated otherwise, only one input per parameter is allowed for each command.
+
+Furthermore, within each command, any other parameters other than the ones listed in each command format are considered invalid.
+The command will not be executed, and an error message will be displayed, if any invalid parameters are present.
+
+
-Shows a message explaning how to access the help page.
+### 4.1 View Help: `help`
-![help message](images/helpMessage.png)
+Brings up the user guide.
Format: `help`
+Example Usage:
+```
+help
+```
-### Adding a person: `add`
+Expected Outcome:
+```
+Opened help window.
+```
-Adds a person to the address book.
+![User Guide](images/userguide/UserGuide.png)
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+The user guide is shown in the application.
-
:bulb: **Tip:**
-A person can have any number of tags (including 0)
-
+### 4.2 Switch Tabs: `tab`
+
+Switches the current tab on Fine$$e.
+
+Format: `tab INDEX`
+* Switches to the tab corresponding to the specified `INDEX`. For the `tab` command, `INDEX` **must be 1, 2, 3 or 4**.
+ * Index 1 switches to the [Overview tab](#221-overview-tab).
+ * Index 2 switches to the [Incomes tab](#222-incomes-tab).
+ * Index 3 switches to the [Expenses tab](#223-expenses-tab).
+ * Index 4 switches to the [Analytics tab](#224-analytics-tab).
+
+Example:
+* `tab 3`
+
+Example Usage:
+```
+tab 1
+```
+
+Expected Outcome:
+```
+Switched to overview tab.
+```
+The application switches to the Overview tab.
+
+### 4.3 Expense
+
+An **expense** represents you *paying money for something*.
+It could be textbooks for the new semester, that cup of bubble tea you drank the other day, or even taking public transport.
+
+To help you manage your finances, Fine\$\$e records the following information about each of your expenses:
+1. **Title**: A descriptive title, to remind you what the expense was about.
+1. **Amount**: How much money you paid, to calculate how much you have spent in total.
+1. **Date**: The date the payment took place, to track your spending over periods of time.
+1. **Categories**: Any number of categories, to help you group related expenses together.
+
+#### 4.3.1 Add Expense: `add-expense`
+
+Adds an expense to the finance tracker.
+
+> :information_source: After executing this command, the UI will automatically switch to the [Expenses tab](#223-expenses-tab).
+
+Format: `add-expense t/TITLE a/AMOUNT [d/DATE] [c/CATEGORY...]`
+
+* `DATE` is optional; if `DATE` is not given, the current date is used.
+* `CATEGORY` is optional. Multiple `c/` prefixes can be used to specify multiple categories.
+
+Shortcuts: `adde`; (when on the [Expenses tab](#223-expenses-tab)) `add`
+
+Examples:
+* `add-expense t/Starbucks Coffee a/8.90 d/25/10/2020`
+* `adde t/Taxi Home from School a/$13 c/Transport c/School`
+
+Example Usage:
+```
+add-expense t/Bubble Tea a/5 d/03/10/2020 c/Food & Beverage
+```
+
+Expected Outcome:
+```
+New expense added: Bubble Tea Amount: $5.00 Date: 03/10/2020 Categories: [Food & Beverage]
+```
+
+![Add Expense](images/userguide/AddExpense.png)
+
+Adds a new expense titled `Bubble Tea`, with amount `$5.00`, date `03/10/2020`, and a single category `Food & Beverage`.
+
+#### 4.3.2 Edit Expense: `edit`
+
+Edits an expense in the finance tracker.
+
+Format: (when on the [Expenses tab](#223-expenses-tab)) `edit INDEX [t/TITLE] [a/AMOUNT] [d/DATE] [c/CATEGORY...]`
+
+* `INDEX` allows you to choose which expense to edit by specifying its position in the currently displayed expenses list.
+* `TITLE`, `AMOUNT`, `DATE` and `CATEGORY` allow you to specify the expense information to update.
+ None of them are mandatory, but at least one must be specified.
+ If any `CATEGORY` is specified, it will replace all of the expense's categories.
+ For parameters that have been omitted, the value will remain unchanged.
+
+> :bulb: To remove all categories from an expense, simply use `c/` with no category name following it.
+
+Examples:
+* `edit 1 t/Shopee Internship d/24/10/2020`
+* `edit 3 a/$2000 c/`
+
+Example Usage:
+```
+edit 2 a/5 d/22/09/2020
+```
+
+Expected Outcome:
+```
+Edited Expense: Artificial Intelligence: A Modern Approach Amount: $5.00 Date: 22/09/2020 Categories: [Textbook]
+```
+
+![Edit Expense](images/userguide/EditExpense.png)
+
+Edits the second expense in the currently displayed expenses list to have amount `$5.00` and date `22/09/2020`.
+The rest of the expense information remains unchanged.
+
+> :information_source: After executing this command, any filtering done on the expenses list via the `find` command will be reset, and the expenses list will display all expenses.
+
+#### 4.3.3 Delete Expense: `delete`
+
+Deletes the specified expense from the finance tracker.
+
+Format: (when on the [Expenses tab](#223-expenses-tab)) `delete INDEX`
+
+* `INDEX` allows you to choose which expense to delete by specifying its position in the currently displayed expenses list.
+
+Example:
+* `delete 3`
+
+Example Usage:
+```
+delete 1
+```
+
+Expected Outcome:
+```
+Deleted Expense: Bubble Tea Amount: $5.00 Date: 22/09/2020 Categories: [Food & Beverage]
+```
+Deletes the first expense in the currently displayed expenses list.
+
+#### 4.3.4 List Expense: `ls-expense`
+
+Shows a list of all expenses in the finance tracker.
+This effectively resets any current filtering of the expenses list, such as those made by `find`.
+
+> :information_source: After executing this command, the UI will automatically switch to the [Expenses tab](#223-expenses-tab).
+
+Format: `ls-expense`
+
+Shortcuts: `lse`; (when on the [Expenses tab](#223-expenses-tab)) `list`
+
+> :bulb: When on the [Overview tab](#221-overview-tab), you can use `list` to list all transactions.
+
+Example Usage:
+```
+list
+```
+
+Expected Outcome:
+```
+Listed all expenses.
+```
+Lists all expenses in the finance tracker.
+
+#### 4.3.5 Find Expense: `find`
+
+Finds expenses that match any of the given search parameters, and displays the list of matching expenses.
+
+Format: (when on the [Expenses tab](#223-expenses-tab)) `find [t/TITLE_KEYPHRASE...] [a/AMOUNT] [d/DATE] [c/CATEGORY...] [af/AMOUNT_FROM] [at/AMOUNT_TO] [df/DATE_FROM] [dt/DATE_TO]`
+
+> :bulb: When on the [Overview tab](#221-overview-tab), you can use `find` to search for all transactions (expenses and incomes).
+
+The available search parameters and their uses are as follows:
+
+* `t/TITLE_KEYPHRASE`: Searches for all expenses with titles containing the specified `TITLE_KEYPHRASE` keyphrase.
+ * The search is case-insensitive. e.g `t/taxi` will match `Taxi`.
+ * A keyphrase can contain more than one word. e.g. `t/bubble tea` is considered as one keyphrase.
+ * The order of the keyphrases does not matter. e.g. `t/Bus t/Train` will match `Train Bus`.
+ * Partial matches are accepted. e.g. `t/Snack` will match `Snacks`.
+ * Titles matching at least one keyphrase will be returned (i.e. `OR` search).
+ e.g. `t/Tea t/Coffee` will return `Bubble Tea`, `Starbucks Coffee`.
+* `a/AMOUNT`: Searches for all expenses with the specified amount.
+* `d/DATE`: Searches for all expenses that occurred on the specified date.
+* `c/CATEGORY`: Searches for all expenses labelled with the specified category.
+ * The search is case-insensitive. e.g `taxi` will match `Taxi`.
+ * More than one category can be given; the search will return expenses with any of the input categories.
+* `af/AMOUNT_FROM`: Searches for all expenses with amounts more than or equal to the specified amount.
+* `at/AMOUNT_TO`: Searches for all expenses with amounts less than or equal to the specified amount.
+* `df/DATE_FROM`: Searches for all expenses that occurred on or later than the specified date.
+* `dt/DATE_TO`: Searches for all expenses that occurred on or before the specified date.
+
+None of the search parameters are mandatory, but at least one must be specified.
Examples:
-* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01`
-* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal`
+* `find t/tea d/22/09/2000`
+* `find c/Food & drink af/10`
+
+Example Usage:
+```
+find t/tea t/coffee a/5 df/01/09/2020 dt/30/09/2020
+```
+
+Expected Outcome:
+```
+2 transactions listed!
+```
+Lists all expenses with titles containing `tea` and/or `coffee`, with amount equal to `$5.00`, in September 2020.
-### Listing all persons : `list`
+### 4.4 Income
-Shows a list of all persons in the address book.
+An **income** represents you *receiving money for something*.
+It could be your internship allowance, prize money you won from a pitch competition, or even red packets that you received over Chinese New Year.
-Format: `list`
+To help you manage your finances, Fine\$\$e records the following information about each of your incomes:
+1. **Title**: A descriptive title, to remind you what the income was about.
+1. **Amount**: How much money you received, to calculate how much you have received in total.
+1. **Date**: The date you received the money, to track your income over periods of time.
+1. **Categories**: Any number of categories, to help you group related incomes together.
-### Editing a person : `edit`
+#### 4.4.1 Add Income: `add-income`
-Edits an existing person in the address book.
+Adds an income to the finance tracker.
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+> :information_source: After executing this command, the UI will automatically switch to the [Incomes tab](#222-incomes-tab).
-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …
-* At least one of the optional fields must be provided.
-* Existing values will be updated to the input values.
-* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative.
-* You can remove all the person’s tags by typing `t/` without
- specifying any tags after it.
+Format: `add-income t/TITLE a/AMOUNT d/DATE [c/CATEGORY...]`
+
+* `DATE` is optional; if `DATE` is not given, the current date is used.
+* `CATEGORY` is optional. Multiple `c/` prefixes can be used to specify multiple categories.
+
+Shortcuts: `addi`; (when on the [Incomes tab](#222-incomes-tab)) `add`
Examples:
-* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively.
-* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags.
+* `add-income t/Waitressing a/80 c/Work`
+* `addi t/Angpao money a/$20 d/25/01/2020 c/CNY c/Gift`
+
+Example Usage:
+```
+add-income t/Shopee Internship a/560 d/03/10/2020 c/Work
+```
+
+Expected Outcome:
+```
+New income added: Shopee Internship Amount: $560.00 Date: 03/10/2020 Categories: [Work]
+```
-### Locating persons by name: `find`
+![Add Income](images/userguide/AddIncome.png)
-Finds persons whose names contain any of the given keywords.
+Adds a new income titled `Shopee Internship`, with amount `$560.00`, date `03/10/2020`, and a single category `Work`.
-Format: `find KEYWORD [MORE_KEYWORDS]`
+#### 4.4.2 Edit Income: `edit`
-* The search is case-insensitive. e.g `hans` will match `Hans`
-* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans`
-* Only the name is searched.
-* Only full words will be matched e.g. `Han` will not match `Hans`
-* Persons matching at least one keyword will be returned (i.e. `OR` search).
- e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`
+Edits an expense in the finance tracker.
+
+Format: (when on the [Incomes tab](#222-incomes-tab)) `edit INDEX [t/TITLE] [a/AMOUNT] [d/DATE] [c/CATEGORY...]`
+
+* `INDEX` allows you to choose which income to edit by specifying its position in the currently displayed incomes list.
+* `TITLE`, `AMOUNT`, `DATE` and `CATEGORY` allow you to specify the updated income information.
+ None of them are mandatory, but at least one must be specified.
+ If any `CATEGORY` is specified, it will replace all of the income's existing categories.
+ For parameters that have been omitted, the value will remain unchanged.
+
+> :bulb: To remove all categories from an income, simply use `c/` with no category name following it.
Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png)
+* `edit 2 t/October Allowance c/Allowance`
+* `edit 1 a/5 d/22/09/2020`
+
+Example Usage:
+```
+edit 3 a/$2000 c/
+```
+
+Expected Outcome:
+```
+Edited Income: Start-up Funding Amount: $2000.00 Date: 01/10/2020 Categories:
+```
+
+![Edit Income](images/userguide/EditIncome.png)
+
+Edits the third income in the currently displayed incomes list to have amount `$2000.00` and no categories.
+The rest of the income information remains unchanged.
+
+> :information_source: After executing this command, any filtering done on the incomes list via the `find` command will be reset, and the incomes list will display all incomes.
+
+#### 4.4.3 Delete Income: `delete`
+
+Deletes the specified income from the finance tracker.
+
+Format: (when on the [Incomes tab](#222-incomes-tab)) `delete INDEX`
+
+* `INDEX` allows you to choose which income to delete by specifying its position in the currently displayed incomes list.
+
+Example:
+* `delete 3`
+
+Example Usage:
+```
+delete 1
+```
+
+Expected Outcome:
+```
+Deleted Income: Teaching Assistant Amount: $1920.00 Date: 18/10/2020 Categories: [CS2103T][CS1101S]
+```
+Deletes the second income in the currently displayed incomes list.
+
+#### 4.4.4 List Income: `ls-income`
+
+Shows a list of all the incomes in the finance tracker.
+This effectively resets any current filtering of the incomes list, such as those made by `find`.
+
+> :information_source: After executing this command, the UI will automatically switch to the [Incomes tab](#222-incomes-tab).
+
+Format: `ls-income`
+
+Shortcuts: `lsi`; (when on the [Incomes tab](#222-incomes-tab)) `list`
+
+> :bulb: When on the [Overview tab](#221-overview-tab), you can use `list` to list all transactions.
+
+Example Usage:
+```
+list
+```
+
+Expected Outcome:
+```
+Listed all incomes.
+```
+Lists all incomes in the finance tracker.
+
+#### 4.4.5 Find Income: `find`
+
+Finds incomes that match any of the given search parameters, and displays the list of matching incomes.
+
+Format: (when on the [Incomes tab](#222-incomes-tab)) `find [t/TITLE_KEYPHRASE...] [a/AMOUNT] [d/DATE] [c/CATEGORY...] [af/AMOUNT_FROM] [at/AMOUNT_TO] [df/DATE_FROM] [dt/DATE_TO]`
-### Deleting a person : `delete`
+> :bulb: When on the [Overview tab](#221-overview-tab), you can use `find` to search for all transactions (expenses and incomes).
-Deletes the specified person from the address book.
+The available search parameters and their uses are as follows:
-Format: `delete INDEX`
+* `t/TITLE_KEYPHRASE`: Searches for all incomes with titles containing the specified `TITLE_KEYPHRASE` keyphrase.
+ * The search is case-insensitive. e.g `t/work` will match `Work`.
+ * A keyphrase can contain more than one word. e.g. `t/Shopee internship` is considered as one keyphrase.
+ * The order of the keyphrases does not matter. e.g. `t/TA t/part-time` will match `part-time TA`.
+ * Partial matches are accepted. e.g. `t/intern` will match `internship`.
+ * Titles matching at least one keyphrase will be returned (i.e. `OR` search).
+ e.g. `t/allowance t/prize` will return `Laptop Allowance`, `Hackathon Prize`.
+* `a/AMOUNT`: Searches for all incomes with the specified amount.
+* `d/DATE`: Searches for all incomes that occurred on the specified date.
+* `c/CATEGORY`: Searches for all incomes labelled with the specified category.
+ * The search is case-insensitive. e.g `work` will match `Work`.
+ * More than one category can be given; the search will return incomes with any of the input categories.
+* `af/AMOUNT_FROM`: Searches for all incomes with amounts more than or equal to the specified amount.
+* `at/AMOUNT_TO`: Searches for all incomes with amounts less than or equal to the specified amount.
+* `df/DATE_FROM`: Searches for all incomes that occurred on or later than the specified date.
+* `dt/DATE_TO`: Searches for all incomes that occurred on or before the specified date.
-* Deletes the person at the specified `INDEX`.
-* The index refers to the index number shown in the displayed person list.
-* The index **must be a positive integer** 1, 2, 3, …
+None of the search parameters are mandatory, but at least one must be specified.
Examples:
-* `list` followed by `delete 2` deletes the 2nd person in the address book.
-* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command.
+* `find t/allowance d/01/10/2020`
+* `find c/angpao at/20`
-### Clearing all entries : `clear`
+Example Usage:
+```
+find c/prize c/gift af/10 at/100
+```
-Clears all entries from the address book.
+Expected Outcome:
+```
+2 transactions listed!
+```
+Lists all incomes with the category `prize` and/or `gift`, with amounts between `$10.00` and `$100.00` inclusive.
+
+### 4.5 Bookmark Expense
+
+A **bookmark expense** is a template that allows you to create expenses that you make frequently, such as paying phone bills monthly or buying bubble tea weekly.
+You can convert a bookmark expense repeatedly to create as many expenses as you want, and the created expenses will automatically be added to Fine$$e's expenses list.
+
+To help you manage your finances, Fine\$\$e records the following information about each of your bookmark expenses:
+1. **Title**: A descriptive title, to remind you what the bookmark expense is about.
+1. **Amount**: How much money you will spend each time you convert the bookmark expense into an expense.
+1. **Categories**: Any number of categories, to help you group related incomes together.
+
+![Bookmark Expense Overview](images/userguide/bookmark/AnnotatedBookmarkExpenseOverview.png)
+
+{:.image-caption}
+Bookmark Expenses List Panel in the Expenses Tab
+
+#### 4.5.1 Add Bookmark Expense: `add-bookmark-expense`
+
+Adds a bookmark expense to the finance tracker.
+
+> :information_source: After executing this command, the UI will automatically switch to the [Expenses tab](#223-expenses-tab).
+
+Format: `add-bookmark-expense t/TITLE a/AMOUNT [c/CATEGORY...]`
+
+Shortcut: `addbe t/TITLE a/AMOUNT [c/CATEGORY...]`
+
+* `CATEGORY` is optional. Multiple `c/` prefixes can be used to specify multiple categories.
+
+> :warning: Adding of duplicate bookmark expenses is not allowed.
+> Bookmark expenses with the same titles are considered to be duplicates of each other.
+> Contiguous whitespace characters between words in the title will be treated as a single whitespace character.
+> e.g. `Phone Bill` and `Phone Bill` are considered to be the same title, while `Phone Bill` and `PhoneBill` are not considered to be the same title.
+
+Examples:
+* `add-bookmark-expense t/Phone Bill a/60 c/Utilities c/Personal`
+* `add-bookmark-expense t/Spotify Subscription a/$9 c/Others`
+* `addbe t/Bubble Tea a/$4.50 c/Food & Beverage`
+* `addbe t/Lunch a/$5.00`
+
+Example Usage:
+```
+add-bookmark-expense t/Netflix Subscription a/11.98 c/Entertainment
+```
+
+Expected Outcome:
+```
+New bookmark expense added: Netflix Subscription Amount: $11.98 Categories: [Entertainment]
+```
+
+![Add Bookmark Expense](images/userguide/bookmark/AnnotatedAddBookmarkExpense.png)
+
+Adds a bookmark expense titled `Netflix Subscription` with amount `$11.98` and one category `Entertainment`.
+
+#### 4.5.2 Edit Bookmark Expense: `edit-bookmark`
+
+Edits a bookmark expense in the finance tracker.
+
+> :warning: This command can only be executed on the [Expenses tab](#223-expenses-tab).
+
+> :warning: Bookmark expenses with the same titles are considered to be duplicates of each other.
+> Therefore, editing the title of a bookmark expense to an already existing title in the bookmark expenses list is not allowed.
+> Contiguous whitespace characters between words in the title will be treated as a single whitespace character.
+
+Format: `edit-bookmark INDEX [t/TITLE] [a/AMOUNT] [c/CATEGORY...]`
+
+* `INDEX` allows you to choose which bookmark expense to edit by specifying its position in the bookmark expenses list.
+* `TITLE`, `AMOUNT` and `CATEGORY` allow you to specify the updated bookmark expense information.
+ None of them are mandatory, but at least one must be specified.
+ If any `CAEGORY` is specified, it will replace all of the bookmark expense's existing categories.
+ For parameters that have been omitted, the value will remain unchanged.
+
+> :bulb: To remove all categories from a bookmark expense, simply use `c/` with no category name following it.
+
+Examples:
+* `edit-bookmark 1 a/65`
+* `edit-bookmark 2 t/Part Time a/400 c/Work`
+* `edit-bookmark 3 c/Others`
+
+Example Usage:
+```
+edit-bookmark 3 a/15 c/Entertainment c/Personal
+```
+
+Expected Outcome:
+```
+Edited Bookmark Expense: Netflix Subscription Amount: $15.00 Categories: [Entertainment][Personal]
+```
+
+![Edit Bookmark Expense](images/userguide/bookmark/AnnotatedEditBookmarkExpense.png)
+
+Edits the third bookmark expense in the bookmark expenses list to have an amount of `$15.00` and two categories `Entertainment` and `Personal`.
+
+#### 4.5.3 Delete Bookmark Expense: `delete-bookmark`
+
+Deletes the specified bookmark expense from the finance tracker.
+
+> :warning: This command can only be executed on the [Expenses tab](#223-expenses-tab).
+
+Format: `delete-bookmark INDEX`
+
+* `INDEX` allows you to choose which bookmark expense to delete by specifying its position in the bookmark expenses list.
+
+Example:
+* `delete-bookmark 2`
+
+Example Usage:
+```
+delete-bookmark 3
+```
+
+Expected Outcome:
+```
+Deleted Bookmark Expense: Phone Bill Amount: $60.00 Categories: [Utilities]
+```
+Deletes the third bookmark expense in the bookmark expenses list.
+
+#### 4.5.4 Convert Bookmark Expense: `convert-bookmark`
+
+Converts a bookmark expense into an expense, and adds it to the expenses list.
+
+> :warning: This command can only be executed on the [Expense tab](#223-expenses-tab).
+
+Format: `convert-bookmark INDEX [d/DATE]`
+
+Shortcut: `convertb INDEX [d/DATE]`
+
+* `INDEX` allows you to choose which bookmark expense to convert by specifying its position in the bookmark expenses list.
+* `DATE` is optional; if `DATE` is not specified, the current date is used.
+
+Examples:
+* `convert-bookmark 2 d/10/10/2020`
+* `convertb 1 d/05/05/2020`
+
+Example Usage:
+```
+convert-bookmark 3 d/10/08/2020
+```
+
+Expected Outcome:
+```
+Bookmark expense has been converted and successfully added to finance tracker: Netflix Subscription Amount: $15.00 Date: 10/08/2020 Categories: [Entertainment][Personal]
+```
+
+![Convert Bookmark Expense](images/userguide/bookmark/AnnotatedConvertBookmarkExpense.png)
+
+Converts the third bookmark expense in the bookmark expenses list into an expense with the information of the
+specified bookmark expense and date `10/08/2020`, and adds it to the expenses list in the finance tracker.
+
+### 4.6 Bookmark Income
+
+A **bookmark income** is a template that allows you to create incomes that you make frequently, such as monthly salary or stipend for being a teaching assistant.
+You can convert a bookmark income repeatedly to create as many incomes as you want, and the created incomes will automatically be added to Fine$$e's incomes list.
+
+To help you manage your finances, Fine\$\$e records the following information about each of your bookmark incomes:
+1. **Title**: A descriptive title, to remind you what the bookmark income is about.
+1. **Amount**: How much money you will receive each time you convert the bookmark income into an income.
+1. **Categories**: Any number of categories, to help you group related incomes together.
+
+![Overview Bookmark Income Panel](images/userguide/bookmark/AnnotatedBookmarkIncomeOverview.png)
+
+{:.image-caption}
+Bookmark Incomes List Panel in the Incomes Tab
+
+#### 4.6.1 Add Bookmark Income: `add-bookmark-income`
+
+Adds a bookmark income to the finance tracker.
+
+> :information_source: After executing this command, the UI will automatically switch to the [Incomes tab](#222-incomes-tab).
+
+Format: `add-bookmark-income t/TITLE a/AMOUNT [c/CATEGORY...]`
+
+Shortcut: `addbi t/TITLE a/AMOUNT [c/CATEGORY...]`
+
+* `CATEGORY` is optional. Multiple `c/` prefixes can be used to specify multiple categories.
+
+> :warning: Adding of duplicate bookmark incomes is not allowed.
+> Bookmark incomes with the same titles are considered to be duplicates of each other.
+> Contiguous whitespace characters between words in the title will be treated as a single whitespace character.
+> e.g. `Part Time` and `Part Time` are considered to be the same title, while `Part Time` and `PartTime` are not considered to be the same title.
+
+Examples:
+* `add-bookmark-income t/Internship a/$1000 c/Work`
+* `add-bookmark-income t/Part Time a/450 c/Work c/Startup`
+* `addbi t/Investments a/400 c/Personal c/Dividends`
+* `addbi t/Monthly Allowance a/300`
+
+Example Usage:
+```
+add-bookmark-income t/Part Time a/400 c/Work
+```
+
+Expected Outcome:
+```
+New bookmark income added: Part Time Amount: $400.00 Categories: [Work]
+```
+
+![Add Bookmark Income Outcome](images/userguide/bookmark/AnnotatedAddBookmarkIncome.png)
+
+Adds a bookmark income titled `Part Time` with amount `$400.00` and one category `Work`.
+
+#### 4.6.2 Edit Bookmark Income: `edit-bookmark`
+
+Edits a bookmark income in the finance tracker.
+
+> :warning: This command can only be executed on the [Incomes tab](#222-incomes-tab).
+
+> :warning: Bookmark incomes with the same titles are considered to be duplicates of each other.
+> Therefore, editing the title of a bookmark income to an already existing title in the bookmark incomes list is not allowed.
+> Contiguous whitespace characters between words in the title will be treated as a single whitespace character.
+
+Format: `edit-bookmark INDEX [t/TITLE] [a/AMOUNT] [c/CATEGORY...]`
+
+* `INDEX` allows you to choose which bookmark income to edit by specifying its position in the bookmark incomes list.
+* `TITLE`, `AMOUNT` and `CATEGORY` allow you to specify the updated bookmark income information.
+ None of them are mandatory, but at least one must be specified.
+ If any `CATEGORY` is specified, it will replace all of the bookmark income's existing categories.
+ For parameters that have been omitted, the value will remain unchanged.
+
+> :bulb: To remove all categories from a bookmark income, simply use `c/` with no category name following it.
+
+Examples:
+* `edit-bookmark 1 t/Monthly Tuition c/Work c/Part Time`
+* `edit-bookmark 2 a/1200`
+* `edit-bookmark 3 t/Investments a/$300.00`
+
+Example Usage:
+```
+edit-bookmark 3 a/500 c/Work c/Part Time Intern
+```
+
+Expected Outcome:
+```
+Edited Bookmark Income: Part Time Amount: $500.00 Categories: [Work][Part Time Intern]
+```
+
+![Edit Bookmark Income](images/userguide/bookmark/AnnotatedEditBookmarkIncome.png)
+
+Edits the third bookmark income in the bookmark incomes list to have an amount of `$500.00` and two categories `Work` and `Part Time Intern`.
+
+#### 4.6.3 Delete Bookmark Income: `delete-bookmark`
+
+Deletes the bookmark income and all of its information from the bookmark income list in Fine$$e.
+
+> :warning: This command can only be executed on the [Incomes tab](#222-incomes-tab).
+
+Format: `delete-bookmark INDEX`
+
+* `INDEX` allows you to choose which bookmark income to delete by specifying its position in the bookmark incomes list.
+
+Example:
+* `delete-bookmark 1`
+
+Example Usage:
+```
+delete-bookmark 2
+```
+
+Expected Outcome:
+```
+Deleted Bookmark Income: Teaching Assistant Amount: $1890.00 Categories: [CS1101S][CS1231S]
+```
+Deletes the second bookmark expense in the bookmark incomes list.
+
+#### 4.6.4 Convert Bookmark Income: `convert-bookmark`
+
+Converts a bookmark income into an income, and adds it to the incomes list.
+
+> :warning: This command can only be executed on the [Incomes tab](#222-incomes-tab).
+
+Format: `convert-bookmark INDEX [d/DATE]`
+
+Shortcut: `convertb INDEX [d/DATE]`
+
+* `INDEX` refers to the index number of the bookmark income shown in the bookmark incomes list.
+* `DATE` is optional; if `DATE` is not specified, the current date is used.
+
+Examples:
+* `convert-bookmark 1 d/10/10/2020`
+* `convertb 2 d/15/10/2020`
+
+Example Usage:
+```
+convert-bookmark 3 d/03/08/2020
+```
+
+Expected Outcome:
+```
+Bookmark income has been converted and successfully added to finance tracker: Part Time Amount: $500.00 Date: 03/08/2020 Categories: [Work][Part Time Intern]
+```
+
+![Convert Bookmark Income](images/userguide/bookmark/AnnotatedConvertBookmarkIncome.png)
+
+Converts the third bookmark income in the bookmark incomes list into an income with the information of the
+specified bookmark income and date `03/08/2020`, then adds it to the incomes list in the finance tracker.
+
+### 4.7 Expense Limit
+
+Ever feel like your wallet just won't stop bleeding? Try setting a monthly expense limit!
+The expense limit feature allows you to limit your spending by setting a budget for the month.
+The monthly expense limit is visible on the Overview tab along with your remaining budget for this month, which is automatically calculated based on the expenses in the finance tracker.
+
+#### 4.7.1 Set Expense Limit: `set-expense-limit`
+
+Sets the monthly expense limit in the finance tracker.
+
+> :information_source: After executing this command, the UI will automatically switch to the [Overview tab](#221-overview-tab).
+
+Format: `set-expense-limit a/AMOUNT`
+
+Shortcut: `setel a/AMOUNT`
+
+Example:
+* `set-expense-limit a/500`
+
+Example Usage:
+```
+setel a/$400.00
+```
+
+Expected Outcome:
+```
+New monthly expense limit set: $400.00
+```
+
+![Set Monthly Expense Limit](images/userguide/SetExpenseLimit.png)
+
+Sets the monthly expense limit in the finance tracker to be `$400.00`.
+
+### 4.8 Savings Goal
+
+Want to save up for the new PlayStation 5 but can't seem to no matter what? Fine$$e has you covered!
+The savings goal feature allows you to save consistently by setting a monthly savings goal, so that you can save up bit by bit and build good financial habits.
+The monthly savings goal visible on the Overview tab along with your current savings for this month, which is automatically calculated based on the expenses and incomes in the finance tracker.
+
+#### 4.8.1 Set Savings Goal: `set-savings-goal`
+
+Sets the monthly savings goal in the finance tracker.
+
+> :information_source: After executing this command, the UI will automatically switch to the [Overview tab](#221-overview-tab).
+
+Format: `set-savings-goal a/AMOUNT`
+
+Shortcut: `setsg a/AMOUNT`
+
+Example:
+* `set-savings-goal a/100`
+
+Example Usage:
+```
+setsg a/$50.00
+```
+
+Expected Outcome:
+```
+New monthly savings goal set: $50.00
+```
+
+![Set Savings Goal](images/userguide/SetSavingsGoal.png)
+
+Sets the monthly savings goal in the finance tracker to be `$50.00`.
+
+### 4.9 Analytics
+
+Want to see your spending/saving trends?
+The Analytics feature will allow you to view a variety of trends based on the incomes/expenses you have entered into the finance tracker.
+These analytics will give you a better overview of your incomes/expenses and help you better understand them, so that you can make targeted changes to your habits if need be.
+
+There are three bar charts that show the following data over the past three months:
+* Total monthly expenses
+* Total monthly incomes
+* Total monthly savings
+
+Whenever you switch to the [Analytics tab](#224-analytics-tab), the data in the bar charts is automatically updated to include any recent modifications you have made in the finance tracker.
+This ensures that you will always see the latest information about your spending and saving trends.
+
+### 4.10 Clear Data: `clear`
+
+Ready to start using Fine\\$\\$e after fiddling with the sample data? Want to start over your finance tracking journey on a clean slate?
+This command allows you to clear *all* existing data inside the finance tracker. That includes:
+* Expenses and incomes
+* Bookmark expenses and bookmark incomes
+* Monthly expense limit
+* Monthly savings goal
Format: `clear`
-### Exiting the program : `exit`
+> :warning: This is a powerful command. You might lose your precious data if you are not careful with it.
+
+> :bulb: If there is any data you would like to keep before clearing, save it somewhere so that you can re-enter it later.
+
+To protect against accidental usage, this command needs to be entered **twice in succession** before it takes effect.
+A single `clear` command is nullified when not followed by another `clear` command.
+
+Here are some example scenarios to illustrate:
+* Scenario A: Enter `clear`.
+ * The finance tracker data is unchanged.
+ * Fine\\$\\$e requests for a second `clear` command as confirmation.
+* Scenario B: Enter `clear`, then enter `clear` again.
+ * The finance tracker data is cleared.
+* Scenario C: Enter `clear`, then enter `lsi` (a valid command), then enter `clear` again.
+ * The finance tracker data is unchanged.
+ * The first `clear` command has been nullified, so Fine\\$\\$e requests for a second `clear` command as confirmation.
+* Scenario D: Enter `clear`, then enter `foo` (an invalid command), then enter `clear` again.
+ * The finance tracker data is unchanged.
+ * The first `clear` command has been nullified, so Fine\\$\\$e requests for a second `clear` command as confirmation.
+* Scenario E: Enter `clear`, then enter `lsi`, then enter `clear` again, then enter `clear` again.
+ * The finance tracker data is cleared.
+
+Example Usage:
+```
+clear
+```
+
+Expected Outcome:
+```
+Please enter 'clear' again to confirm your decision.
+```
+
+Example Usage (continued):
+```
+clear
+```
+
+Expected Outcome:
+```
+Finance tracker has been cleared!
+```
+All data in the finance tracker is cleared.
+
+### 4.11 Exit the Program: `exit`
Exits the program.
Format: `exit`
-### Saving the data
+### 4.12 Command History
+
+Accidentally entered the wrong command and wish to modify it without typing it out again fully?
+Simply press the ↑ or ↓ arrow keys on your keyboard to navigate through your command history.
+
+* The command history keeps track of the latest 50 commands entered in the current session.
+* The command input box must be focused on, i.e. ensure that the text cursor is blinking in the command input box.
+* Press the ↑ arrow key to retrieve the previous commands.
+ * Each press of the ↑ arrow key retrieves the command immediately preceding the current command.
+ * If the current command is the earliest command that is being tracked, then pressing the ↑ arrow key does nothing.
+* Press the ↓ arrow key to retrieve the next commands.
+ * Each press of the ↓ arrow key retrieves the command immediately succeeding the current command.
+ * If the current command is the latest command that has been entered, then the command input box will be cleared upon pressing the ↓ arrow key.
+
+> :warning: Pressing either the ↑ or ↓ arrow keys will cause whatever text is currently in the command box to be overwritten.
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+### 4.13 Saving the Data
-### Archiving data files `[coming in v2.0]`
+Fine$$e data is saved in the hard disk automatically after any command that changes the data.
+There is no need to save manually.
-_{explain the feature here}_
+--------------------------------------------------------------------------------------------------------------------
+
+## 5. Command Summary
+
+Action | Format | Examples
+------|------|--------
+Add Expense | `add-expense t/TITLE a/AMOUNT [d/DATE] [c/CATEGORY...]` `adde t/TITLE a/AMOUNT [d/DATE] [c/CATEGORY...]` (On Expenses tab) `add t/TITLE a/AMOUNT [d/DATE] [c/CATEGORY...]` | `add-expense t/Bubble Tea a/5 d/03/10/2020 c/Food & Beverage` `adde t/Taxi Home from School a/$13.50 d/10/10/2020 c/Transport c/School`
+Add Income | `add-income t/TITLE a/AMOUNT [d/DATE] [c/CATEGORY...]` `addi t/TITLE a/AMOUNT [d/DATE] [c/CATEGORY...]` (On Income tab) `add t/TITLE a/AMOUNT [d/DATE] [c/CATEGORY...]` | `add-income t/Internship a/560 d/03/10/2020 c/Work` `addi t/Angpao money a/$20 d/10/10/2020 c/CNY c/Gift`
+List Expenses | `ls-expense` `lse` (On Expenses tab) `list`
+List Incomes | `ls-income` `lsi` (On Incomes tab) `list`
+Find | `find [t/TITLE_KEYPHRASE...] [a/AMOUNT] [d/DATE] [c/CATEGORY...] [af/AMOUNT_FROM] [at/AMOUNT_TO] [df/DATE_FROM] [dt/DATE_TO]` | `find c/Food & drink af/10` `find t/allowance d/01/10/2020`
+Edit | `edit INDEX [t/TITLE] [a/AMOUNT] [d/DATE] [c/CATEGORY...]`| `edit 1 t/Brunch d/22/09/2020` `edit 2 a/500 c/`
+Delete | `delete INDEX` | `delete 1`
+Add Bookmark Expense | `add-bookmark-expense t/TITLE a/AMOUNT [c/CATEGORY...]` `addbe t/TITLE a/AMOUNT [c/CATEGORY...]` | `add-bookmark-expense t/Phone Bill a/60 c/Utilities c/Personal` `addbe t/Bubble Tea a/$4.50 c/Food & Beverage`
+Add Bookmark Income | `add-bookmark-income t/TITLE a/AMOUNT [c/CATEGORY...]` `addbi t/TITLE a/AMOUNT [c/CATEGORY...]` | `add-bookmark-income t/Internship a/$1000 c/Work` `addbi t/Investments a/400 c/Personal c/Dividends`
+Edit Bookmark | `edit-bookmark INDEX [t/TITLE] [a/AMOUNT] [c/CATEGORY...]`| `edit-bookmark 1 t/Monthly Tuition c/Work c/Part Time` `edit-bookmark 2 a/1200`
+Delete Bookmark | `delete-bookmark INDEX` | `delete-bookmark 2`
+Convert Bookmark | `convert-bookmark INDEX [d/DATE]` `convertb INDEX [d/DATE]` | `convert-bookmark 2 d/10/10/2020` `convertb 1 d/05/05/2020`
+Set Expense Limit | `set-expense-limit a/AMOUNT` `setel a/AMOUNT` | `set-expense-limit a/500` `setel a/$400.00`
+Set Savings Goal | `set-savings-goal a/AMOUNT` `setsg a/AMOUNT` | `set-savings-goal a/100` `setsg a/$50.00`
+Tab | `tab INDEX` | `tab 2`
+Help | `help`
+Exit | `exit`
--------------------------------------------------------------------------------------------------------------------
-## FAQ
+## 6. Glossary
-**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder.
+Term | Definition
+-----|-----------
+Bookmark Expense | A template for an expense, which can be used to create expenses that are added to the finance tracker.
+Bookmark Income | A template for an income, which can be used to create incomes that are added to the finance tracker.
+Current Date | The system date on the computer on which Fine$$e is set up.
+Expense | A one-time transaction that results in a decrease in the amount of money you have.
+Income | A one-time transaction that results in an increase in the amount of money you have.
+Text Cursor | A blinking vertical line that indicates where text will be placed when entered.
--------------------------------------------------------------------------------------------------------------------
-## Command summary
-
-Action | Format, Examples
---------|------------------
-**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…` e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague`
-**Clear** | `clear`
-**Delete** | `delete INDEX` e.g., `delete 3`
-**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…` e.g.,`edit 2 n/James Lee e/jameslee@example.com`
-**Find** | `find KEYWORD [MORE_KEYWORDS]` e.g., `find James Jake`
-**List** | `list`
-**Help** | `help`
+## 7. FAQ
+
+**Q**: How do I transfer my data to another computer?
+
+**A**: Install the app on the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Fine$$e finance tracker in the home folder.
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..813c173d726 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,15 +1,30 @@
-title: "AB-3"
+title: "Fine$$e"
theme: minima
header_pages:
+ - GanttChart.md
+ - Minutes.md
- UserGuide.md
- DeveloperGuide.md
- AboutUs.md
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+kramdown:
+ parse_block_html: true
+
+repository: "AY2021S1-CS2103T-W16-3/tp"
github_icon: "images/github-icon.png"
plugins:
- - jemoji
+ - jekyll-coffeescript
+ - jekyll-default-layout
+ - jekyll-gist
+ # Disabled since we are building the files manually.
+ # - jekyll-github-metadata
+ - jekyll-optional-front-matter
+ - jekyll-paginate
+ - jekyll-readme-index
+ - jekyll-titles-from-headings
+ - jekyll-relative-links
+ - jekyll-spaceship
diff --git a/docs/_includes/custom-head.html b/docs/_includes/custom-head.html
index 8559a67ffad..6427a801b2d 100644
--- a/docs/_includes/custom-head.html
+++ b/docs/_includes/custom-head.html
@@ -4,3 +4,5 @@
1. Head over to https://realfavicongenerator.net/ to add your own favicons.
2. Customize default _includes/custom-head.html in your source directory and insert the given code snippet.
{% endcomment %}
+
+
diff --git a/docs/_includes/head.html b/docs/_includes/head.html
index 83ac5326933..9fc1a64416f 100644
--- a/docs/_includes/head.html
+++ b/docs/_includes/head.html
@@ -7,6 +7,10 @@
{%- include custom-head.html -%}
- {{page.title}}
+ {% if page.layout == 'minutes' %}
+ Minutes for {{ page.date | date: "%-d %B %Y" }}
+ {% else %}
+ {{page.title}}
+ {% endif %}
diff --git a/docs/_layouts/chart.html b/docs/_layouts/chart.html
new file mode 100644
index 00000000000..4770216cd3e
--- /dev/null
+++ b/docs/_layouts/chart.html
@@ -0,0 +1,14 @@
+---
+layout: widescreen
+---
+
+
+
diff --git a/docs/_layouts/widescreen.html b/docs/_layouts/widescreen.html
new file mode 100644
index 00000000000..a0cef963fa7
--- /dev/null
+++ b/docs/_layouts/widescreen.html
@@ -0,0 +1,18 @@
+
+
+
+{%- include head.html -%}
+
+
+
+{%- include header.html -%}
+
+
+
+ {{ content }}
+
+
+
+
+
+
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 06e47f23762..09e181ef9bb 100644
--- a/docs/_sass/minima/_base.scss
+++ b/docs/_sass/minima/_base.scss
@@ -213,6 +213,24 @@ pre {
}
+/**
+ * Wide Wrapper
+ */
+.wide-wrapper {
+ width: calc(#{$content-width} - (#{$spacing-unit}));
+ margin-right: auto;
+ margin-left: auto;
+ padding-right: $spacing-unit / 2;
+ padding-left: $spacing-unit / 2;
+ @extend %clearfix;
+
+ @media screen and (min-width: $on-large) {
+ width: 90vw;
+ padding-right: $spacing-unit;
+ padding-left: $spacing-unit;
+ }
+}
+
/**
* Clearfix
diff --git a/docs/_sass/minima/_layout.scss b/docs/_sass/minima/_layout.scss
index ca99f981701..48cdc03ca32 100644
--- a/docs/_sass/minima/_layout.scss
+++ b/docs/_sass/minima/_layout.scss
@@ -176,7 +176,7 @@
.post-content {
margin-bottom: $spacing-unit;
- h1, h2, h3 { margin-top: $spacing-unit * 2 }
+ h1, h2, h3 { margin-top: $spacing-unit }
h4, h5, h6 { margin-top: $spacing-unit }
h2 {
@@ -261,3 +261,52 @@
width: calc(50% - (#{$spacing-unit} / 2));
}
}
+
+
+
+/**
+ * Table of Contents without bullet points
+ */
+.toc-no-bullet-points ul, .toc-no-bullet-points ol {
+ list-style-type: none;
+}
+
+
+
+/**
+ * Center element
+ */
+.center {
+ margin: 0;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ -ms-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+}
+
+
+
+/**
+ * Styling for text on the 'no external navigation' page
+ */
+.no-navigation {
+ color: #E46C0A;
+ text-align: center;
+ font-size: 40px;
+}
+
+.no-navigation-subtext {
+ text-align: center;
+ margin-bottom: 0px;
+}
+
+
+
+/**
+ * Image caption
+ */
+.image-caption {
+ text-align: center;
+ font-style: italic;
+}
diff --git a/docs/assets/favicon.png b/docs/assets/favicon.png
new file mode 100644
index 00000000000..b4394d69af0
Binary files /dev/null and b/docs/assets/favicon.png differ
diff --git a/docs/diagrams/AddTransactionActivityDiagram.puml b/docs/diagrams/AddTransactionActivityDiagram.puml
new file mode 100644
index 00000000000..a2efada393b
--- /dev/null
+++ b/docs/diagrams/AddTransactionActivityDiagram.puml
@@ -0,0 +1,42 @@
+@startuml
+start
+:User executes add command;
+
+if () then ([add])
+ if () then ([user is not on Expenses or Incomes tab])
+ :Throw a ParseException;
+ stop
+ else ([else])
+ if () then ([user is on Expenses tab])
+ :Use AddExpenseCommandParser;
+ :Add new expense to model;
+ :Display updated expenses list;
+ else ([user is on Incomes tab])
+ :Use AddIncomeCommandParser;
+ :Add new income to model;
+ :Display updated incomes list;
+ endif
+ endif
+
+else ([add-expense or add-income])
+ if () then ([add-expense])
+ :Use AddExpenseCommandParser;
+ :Add new expense to model;
+ if () then ([user is on Expenses tab])
+ else ([else])
+ :Switch to Expenses tab;
+ endif
+ :Display updated expenses list;
+ else ([add-income])
+ :Use AddIncomeCommandParser;
+ :Add new income to model;
+ if () then ([user is on Incomes tab])
+ else ([else])
+ :Switch to Incomes tab;
+ endif
+ :Display updated incomes list;
+ endif
+endif
+
+stop
+@enduml
diff --git a/docs/diagrams/AddTransactionClassDiagram.puml b/docs/diagrams/AddTransactionClassDiagram.puml
new file mode 100644
index 00000000000..db47d3d74b6
--- /dev/null
+++ b/docs/diagrams/AddTransactionClassDiagram.puml
@@ -0,0 +1,55 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR_T4
+skinparam classBackgroundColor LOGIC_COLOR
+
+package Logic {
+
+package Parser {
+Class FinanceTrackerParser
+Class AddExpenseCommandParser
+Class AddIncomeCommandParser
+}
+
+package Command {
+Class AddExpenseCommand
+Class AddIncomeCommand
+}
+}
+
+
+package Model {
+
+package Transaction {
+Class Expense MODEL_COLOR
+Class Income MODEL_COLOR
+Class "{abstract}\nTransaction" as Transaction MODEL_COLOR
+Class Title MODEL_COLOR
+Class Amount MODEL_COLOR
+Class Date MODEL_COLOR
+}
+package Category {
+Class Category MODEL_COLOR
+}
+}
+
+FinanceTrackerParser ..> AddExpenseCommandParser: creates >
+FinanceTrackerParser ..> AddIncomeCommandParser: creates >
+
+AddExpenseCommandParser ..> AddExpenseCommand: creates >
+AddExpenseCommandParser ..> Expense: creates >
+AddIncomeCommandParser ..> AddIncomeCommand: creates >
+AddIncomeCommandParser ..> Income: creates >
+
+AddExpenseCommand --> "1" Expense
+AddIncomeCommand --> "1" Income
+
+Expense --|> Transaction MODEL_COLOR
+Income --|> Transaction MODEL_COLOR
+Transaction *--> "1" Amount MODEL_COLOR
+Transaction *--> "1" Title MODEL_COLOR
+Transaction *--> "1" Date MODEL_COLOR
+Transaction *--> "*" Category MODEL_COLOR
+
+@enduml
diff --git a/docs/diagrams/AddTransactionSequenceDiagram.puml b/docs/diagrams/AddTransactionSequenceDiagram.puml
new file mode 100644
index 00000000000..621636dddec
--- /dev/null
+++ b/docs/diagrams/AddTransactionSequenceDiagram.puml
@@ -0,0 +1,81 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":FinanceTrackerParser" as FinanceTrackerParser LOGIC_COLOR
+participant ":AddExpenseCommandParser" as AddExpenseCommandParser LOGIC_COLOR
+participant "cmd:AddExpenseCommand" as AddExpenseCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "e:Expense" as Expense MODEL_COLOR
+participant ":ModelManager" as ModelManager MODEL_COLOR
+end box
+
+[-> LogicManager : execute("adde ...", uiState)
+activate LogicManager
+
+LogicManager -> FinanceTrackerParser : parseCommand("adde ...", uiState)
+activate FinanceTrackerParser
+
+create AddExpenseCommandParser
+FinanceTrackerParser -> AddExpenseCommandParser
+activate AddExpenseCommandParser
+
+AddExpenseCommandParser --> FinanceTrackerParser
+deactivate AddExpenseCommandParser
+
+FinanceTrackerParser -> AddExpenseCommandParser : parse("...")
+activate AddExpenseCommandParser
+
+ref over AddExpenseCommandParser: Parse user input
+
+' TODO parsing data fields takes place here
+
+create Expense
+AddExpenseCommandParser -> Expense
+activate Expense
+Expense --> AddExpenseCommandParser
+deactivate Expense
+
+create AddExpenseCommand
+AddExpenseCommandParser -> AddExpenseCommand
+activate AddExpenseCommand
+
+AddExpenseCommand --> AddExpenseCommandParser
+deactivate AddExpenseCommand
+
+AddExpenseCommandParser --> FinanceTrackerParser: cmd
+
+deactivate AddExpenseCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+AddExpenseCommandParser -[hidden]-> FinanceTrackerParser
+destroy AddExpenseCommandParser
+
+FinanceTrackerParser --> LogicManager : cmd
+deactivate FinanceTrackerParser
+
+LogicManager -> AddExpenseCommand : execute(model)
+activate AddExpenseCommand
+
+AddExpenseCommand -> ModelManager : addExpense(e)
+activate ModelManager
+
+ModelManager --> AddExpenseCommand
+deactivate ModelManager
+
+create CommandResult
+AddExpenseCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> AddExpenseCommand
+deactivate CommandResult
+
+AddExpenseCommand --> LogicManager : result
+deactivate AddExpenseCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/AnalyticsTabObjectDiagram.puml b/docs/diagrams/AnalyticsTabObjectDiagram.puml
new file mode 100644
index 00000000000..2a2bb9da3c4
--- /dev/null
+++ b/docs/diagrams/AnalyticsTabObjectDiagram.puml
@@ -0,0 +1,56 @@
+@startuml
+left to right direction
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor UI_COLOR_T4
+skinparam classBackgroundColor UI_COLOR
+
+Package UI <>{
+Object ":AnalyticsTabPane" as atp
+Object "expenseAnalyticsBarChart:BarChart" as eabc
+Object "incomeAnalyticsBarChart:BarChart" as iabc
+Object "savingsAnalyticsBarChart:BarChart" as sabc
+Object ":XYChart.Data" as edata
+Object ":XYChart.Data" as idata
+Object ":XYChart.Data" as sdata
+}
+
+Package Budget <>{
+Object ":MonthlyBudget" as mb
+Object "monthlyExpenses:ObservableList" as me
+Object "monthlyIncomes:ObservableList" as mi
+Object "monthlySavings:ObservableList" as ms
+Object "months:ObservableList" as mths
+Object ":CalculatedAmount" as meca
+Object ":CalculatedAmount" as mica
+Object ":CalculatedAmount" as msca
+Object ":String" as mth
+}
+
+atp *-left-> eabc
+atp *-left-> iabc
+atp *-left-> sabc
+
+eabc --> edata
+iabc --> idata
+sabc --> sdata
+
+mb *--> me
+mb *--> mi
+mb *--> ms
+mb *--> mths
+
+me -up-> meca
+mi -up-> mica
+ms -up-> msca
+mths -up-> mth
+
+edata ..> meca
+edata ..> mth
+idata ..> mica
+idata ..> mth
+sdata ..> msca
+sdata ..> mth
+atp ..> mb
+
+@enduml
diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml
index ef81d18c337..26da955062e 100644
--- a/docs/diagrams/ArchitectureSequenceDiagram.puml
+++ b/docs/diagrams/ArchitectureSequenceDiagram.puml
@@ -13,13 +13,13 @@ activate ui UI_COLOR
ui -[UI_COLOR]> logic : execute("delete 1")
activate logic LOGIC_COLOR
-logic -[LOGIC_COLOR]> model : deletePerson(p)
+logic -[LOGIC_COLOR]> model : deleteTransaction(target)
activate model MODEL_COLOR
model -[MODEL_COLOR]-> logic
deactivate model
-logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook)
+logic -[LOGIC_COLOR]> storage : saveFinanceTracker(financeTracker)
activate storage STORAGE_COLOR
storage -[STORAGE_COLOR]> storage : Save to file
diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml
deleted file mode 100644
index 29076104af3..00000000000
--- a/docs/diagrams/BetterModelClassDiagram.puml
+++ /dev/null
@@ -1,21 +0,0 @@
-@startuml
-!include style.puml
-skinparam arrowThickness 1.1
-skinparam arrowColor MODEL_COLOR
-skinparam classBackgroundColor MODEL_COLOR
-
-AddressBook *-right-> "1" UniquePersonList
-AddressBook *-right-> "1" UniqueTagList
-UniqueTagList -[hidden]down- UniquePersonList
-UniqueTagList -[hidden]down- UniquePersonList
-
-UniqueTagList *-right-> "*" Tag
-UniquePersonList o-right-> Person
-
-Person -up-> "*" Tag
-
-Person *--> Name
-Person *--> Phone
-Person *--> Email
-Person *--> Address
-@enduml
diff --git a/docs/diagrams/BudgetActivityDiagram.puml b/docs/diagrams/BudgetActivityDiagram.puml
new file mode 100644
index 00000000000..75182b25664
--- /dev/null
+++ b/docs/diagrams/BudgetActivityDiagram.puml
@@ -0,0 +1,11 @@
+@startuml
+start
+:User executes command;
+
+if () then ([amount changes])
+ :Calculates all budget values;
+ :Displays updated budget values;
+else ()
+endif
+stop
+@enduml
diff --git a/docs/diagrams/BudgetClassDiagram.puml b/docs/diagrams/BudgetClassDiagram.puml
new file mode 100644
index 00000000000..27ccc32af1f
--- /dev/null
+++ b/docs/diagrams/BudgetClassDiagram.puml
@@ -0,0 +1,36 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+Package Model <>{
+Class FinanceTracker
+Class ModelManager
+
+Package Transaction {
+Class Amount
+Class CalculatedAmount
+Class TransactionList
+}
+
+Package Budget {
+Class MonthlyBudget
+}
+}
+
+ModelManager o--> FinanceTracker
+
+FinanceTracker *--> TransactionList
+
+FinanceTracker *--> MonthlyBudget
+CalculatedAmount ..> Amount
+MonthlyBudget --> CalculatedAmount
+MonthlyBudget --> Amount
+MonthlyBudget ..> TransactionList
+
+ModelManager o--> MonthlyBudget
+
+CalculatedAmount -[hidden]right-> Amount
+
+@enduml
diff --git a/docs/diagrams/CommandHistoryClassDiagram.puml b/docs/diagrams/CommandHistoryClassDiagram.puml
new file mode 100644
index 00000000000..41ff1021767
--- /dev/null
+++ b/docs/diagrams/CommandHistoryClassDiagram.puml
@@ -0,0 +1,24 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+Package Model <>{
+ Class ModelManager
+
+ Package CommandHistory {
+ Class CommandHistory
+ Class EvictingStack
+ Class Node
+ }
+}
+
+ModelManager o--> "1" CommandHistory
+CommandHistory o--> "1" EvictingStack
+EvictingStack --> "0..1" Node : Top node
+EvictingStack --> "0..1" Node : Bottom node
+Node --> "0..1" Node : Next node
+Node --> "0..1" Node : Previous node
+
+@enduml
diff --git a/docs/diagrams/CommandHistoryDownActivityDiagram.puml b/docs/diagrams/CommandHistoryDownActivityDiagram.puml
new file mode 100644
index 00000000000..a3085b98782
--- /dev/null
+++ b/docs/diagrams/CommandHistoryDownActivityDiagram.puml
@@ -0,0 +1,10 @@
+@startuml
+start
+:User presses the ↓ arrow key;
+If () then ([current command is the latest tracked command])
+ :Clear command input box;
+else ([else])
+ :Retrieve the command succeeding the current command;
+endif
+stop
+@enduml
diff --git a/docs/diagrams/CommandHistoryUpActivityDiagram.puml b/docs/diagrams/CommandHistoryUpActivityDiagram.puml
new file mode 100644
index 00000000000..be8ae0890db
--- /dev/null
+++ b/docs/diagrams/CommandHistoryUpActivityDiagram.puml
@@ -0,0 +1,10 @@
+@startuml
+start
+:User presses the ↑ arrow key;
+If () then ([current command is the earliest tracked command])
+ :Do nothing;
+else ([else])
+ :Retrieve the command preceding the current command;
+endif
+stop
+@enduml
diff --git a/docs/diagrams/CommandResultSequenceDiagram.puml b/docs/diagrams/CommandResultSequenceDiagram.puml
new file mode 100644
index 00000000000..878be1baae2
--- /dev/null
+++ b/docs/diagrams/CommandResultSequenceDiagram.puml
@@ -0,0 +1,45 @@
+@startuml
+!include style.puml
+
+actor User USER_COLOR
+
+box Ui UI_COLOR_T1
+participant ":MainWindow" as MainWindow UI_COLOR
+end box
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant "c:XYZCommand" as XYZCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+note right of XYZCommand : XYZCommand =\nAddExpenseCommand,\nFindCommand, etc
+
+User -> MainWindow : input command string
+activate MainWindow
+
+MainWindow -> LogicManager : execute()
+activate LogicManager
+
+ref over LogicManager, XYZCommand : Parse user input
+
+LogicManager -> XYZCommand : execute()
+activate XYZCommand
+
+create CommandResult
+XYZCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> XYZCommand : result
+deactivate CommandResult
+
+XYZCommand --> LogicManager : result
+deactivate XYZCommand
+
+LogicManager --> MainWindow : result
+deactivate LogicManager
+
+MainWindow --> User
+deactivate MainWindow
+
+@enduml
diff --git a/docs/diagrams/CommitActivityDiagram.puml b/docs/diagrams/CommitActivityDiagram.puml
deleted file mode 100644
index 7f8fe407f89..00000000000
--- a/docs/diagrams/CommitActivityDiagram.puml
+++ /dev/null
@@ -1,15 +0,0 @@
-@startuml
-start
-:User executes command;
-
-'Since the beta syntax does not support placing the condition outside the
-'diamond we place it as the true branch instead.
-
-if () then ([command commits AddressBook])
- :Purge redunant states;
- :Save AddressBook to
- addressBookStateList;
-else ([else])
-endif
-stop
-@enduml
diff --git a/docs/diagrams/ConvertBookmarkExpenseSequenceDiagram.puml b/docs/diagrams/ConvertBookmarkExpenseSequenceDiagram.puml
new file mode 100644
index 00000000000..66a9342be7f
--- /dev/null
+++ b/docs/diagrams/ConvertBookmarkExpenseSequenceDiagram.puml
@@ -0,0 +1,66 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":ConvertBookmarkExpenseCommand" as ConvertBookmarkExpenseCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":ModelManager" as ModelManager MODEL_COLOR
+end box
+
+box BookmarkExpense BOOKMARK_TRANSACTION__COLOR_T1
+participant ":BookmarkExpenseList" as BookmarkExpenseList BOOKMARK_TRANSACTION_COLOR
+participant ":BookmarkExpense" as BookmarkExpense BOOKMARK_TRANSACTION_COLOR
+end box
+
+[->LogicManager: execute(command)
+activate LogicManager
+
+ref over LogicManager : parse user input to create ConvertBookmarkExpenseCommand
+
+create ConvertBookmarkExpenseCommand
+LogicManager -> ConvertBookmarkExpenseCommand: execute(model)
+activate ConvertBookmarkExpenseCommand
+
+ConvertBookmarkExpenseCommand -> ModelManager: getFilteredBookmarkExpenseList()
+activate ModelManager
+
+ModelManager --> ConvertBookmarkExpenseCommand: bookmarkExpenseList
+deactivate ModelManager
+
+ConvertBookmarkExpenseCommand -> BookmarkExpenseList: bookmarkExpenseList.get()
+activate BookmarkExpenseList
+
+BookmarkExpenseList --> ConvertBookmarkExpenseCommand: bookmarkExpenseToConvert
+deactivate BookmarkExpenseList
+
+ConvertBookmarkExpenseCommand -> BookmarkExpense: bookmarkExpenseToConvert.convert()
+activate BookmarkExpense
+
+BookmarkExpense --> ConvertBookmarkExpenseCommand: newConvertedExpense
+deactivate BookmarkExpense
+
+ConvertBookmarkExpenseCommand -> ModelManager: addExpense(newConvertedExpense)
+activate ModelManager
+
+ModelManager --> ConvertBookmarkExpenseCommand
+deactivate ModelManager
+
+create CommandResult
+ConvertBookmarkExpenseCommand -> CommandResult
+activate CommandResult
+
+
+CommandResult --> ConvertBookmarkExpenseCommand
+deactivate CommandResult
+
+ConvertBookmarkExpenseCommand --> LogicManager: result
+deactivate ConvertBookmarkExpenseCommand
+
+[<-- LogicManager: result
+deactivate LogicManager
+
+@enduml
diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml
index 1dc2311b245..011b50f0a3e 100644
--- a/docs/diagrams/DeleteSequenceDiagram.puml
+++ b/docs/diagrams/DeleteSequenceDiagram.puml
@@ -3,66 +3,83 @@
box Logic LOGIC_COLOR_T1
participant ":LogicManager" as LogicManager LOGIC_COLOR
-participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":FinanceTrackerParser" as FinanceTrackerParser LOGIC_COLOR
participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR
-participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR
+participant ":DeleteCommand" as DeleteCommand LOGIC_COLOR
+participant "cmd:DeleteExpenseCommand" as DeleteExpenseCommand LOGIC_COLOR
participant ":CommandResult" as CommandResult LOGIC_COLOR
end box
box Model MODEL_COLOR_T1
-participant ":Model" as Model MODEL_COLOR
+participant ":ModelManager" as ModelManager MODEL_COLOR
end box
[-> LogicManager : execute("delete 1")
activate LogicManager
-LogicManager -> AddressBookParser : parseCommand("delete 1")
-activate AddressBookParser
+LogicManager -> FinanceTrackerParser : parseCommand("delete 1")
+activate FinanceTrackerParser
create DeleteCommandParser
-AddressBookParser -> DeleteCommandParser
+FinanceTrackerParser -> DeleteCommandParser
activate DeleteCommandParser
-DeleteCommandParser --> AddressBookParser
+DeleteCommandParser --> FinanceTrackerParser
deactivate DeleteCommandParser
-AddressBookParser -> DeleteCommandParser : parse("1")
+FinanceTrackerParser -> DeleteCommandParser : parse("1")
activate DeleteCommandParser
create DeleteCommand
DeleteCommandParser -> DeleteCommand
activate DeleteCommand
-DeleteCommand --> DeleteCommandParser : d
+DeleteCommand --> DeleteCommandParser
deactivate DeleteCommand
-DeleteCommandParser --> AddressBookParser : d
+DeleteCommandParser --> FinanceTrackerParser
deactivate DeleteCommandParser
'Hidden arrow to position the destroy marker below the end of the activation bar.
-DeleteCommandParser -[hidden]-> AddressBookParser
+DeleteCommandParser -[hidden]-> FinanceTrackerParser
destroy DeleteCommandParser
-AddressBookParser --> LogicManager : d
-deactivate AddressBookParser
+create DeleteExpenseCommand
+FinanceTrackerParser -> DeleteExpenseCommand
+activate DeleteExpenseCommand
-LogicManager -> DeleteCommand : execute()
+DeleteExpenseCommand -> DeleteCommand : getTargetIndex()
activate DeleteCommand
-DeleteCommand -> Model : deletePerson(1)
-activate Model
+DeleteCommand --> DeleteExpenseCommand
+deactivate DeleteCommand
+
+DeleteCommand -[hidden]-> FinanceTrackerParser
+destroy DeleteCommand
+
+DeleteExpenseCommand --> FinanceTrackerParser : cmd
+deactivate DeleteExpenseCommand
+
+FinanceTrackerParser --> LogicManager : cmd
+deactivate FinanceTrackerParser
-Model --> DeleteCommand
-deactivate Model
+LogicManager -> DeleteExpenseCommand : execute()
+activate DeleteExpenseCommand
+
+DeleteExpenseCommand -> ModelManager : getFilteredExpenseList()
+activate ModelManager
+
+ModelManager --> DeleteExpenseCommand
+deactivate ModelManager
create CommandResult
-DeleteCommand -> CommandResult
+DeleteExpenseCommand -> CommandResult
activate CommandResult
-CommandResult --> DeleteCommand
+CommandResult --> DeleteExpenseCommand
deactivate CommandResult
-DeleteCommand --> LogicManager : result
-deactivate DeleteCommand
+DeleteExpenseCommand --> LogicManager : result
+deactivate DeleteExpenseCommand
[<--LogicManager
deactivate LogicManager
diff --git a/docs/diagrams/DeleteTransactionActivityDiagram.puml b/docs/diagrams/DeleteTransactionActivityDiagram.puml
new file mode 100644
index 00000000000..08137813a25
--- /dev/null
+++ b/docs/diagrams/DeleteTransactionActivityDiagram.puml
@@ -0,0 +1,21 @@
+@startuml
+start
+:User executes delete command;
+
+if () then ([user is not on Expenses or Incomes tab])
+ :Throw a ParseException;
+ stop
+else ([else])
+ if () then ([user is on Expenses tab])
+ :Use DeleteExpenseCommand;
+ :Delete target expense;
+ :Display updated expenses list;
+ else ([user is on Incomes tab])
+ :Use DeleteIncomeCommand;
+ :Delete target income;
+ :Display updated incomes list;
+ endif
+endif
+
+stop
+@enduml
diff --git a/docs/diagrams/DeleteTransactionClassDiagram.puml b/docs/diagrams/DeleteTransactionClassDiagram.puml
new file mode 100644
index 00000000000..dd3c00eef89
--- /dev/null
+++ b/docs/diagrams/DeleteTransactionClassDiagram.puml
@@ -0,0 +1,45 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR_T4
+skinparam classBackgroundColor LOGIC_COLOR
+
+package Logic {
+
+package Parser {
+Class FinanceTrackerParser
+Class DeleteCommandParser
+}
+
+package Command {
+Class DeleteCommand
+Class DeleteExpenseCommand
+Class DeleteIncomeCommand
+}
+}
+
+
+package Model {
+
+package Transaction {
+Class Expense MODEL_COLOR
+Class Income MODEL_COLOR
+Class "{abstract}\nTransaction" as Transaction MODEL_COLOR
+}
+}
+
+FinanceTrackerParser .> DeleteCommandParser: creates >
+DeleteCommandParser .> DeleteCommand: creates >
+
+FinanceTrackerParser ..> DeleteExpenseCommand: creates >
+FinanceTrackerParser ..> DeleteIncomeCommand: creates >
+
+DeleteExpenseCommand --|> DeleteCommand
+DeleteIncomeCommand --|> DeleteCommand
+DeleteCommand *----> "1" Transaction: deletes >
+note bottom on link: Reference is stored in\nthe form of an Index
+
+Expense -up-|> Transaction MODEL_COLOR
+Income -up-|> Transaction MODEL_COLOR
+
+@enduml
diff --git a/docs/diagrams/EditTransactionActivityDiagram.puml b/docs/diagrams/EditTransactionActivityDiagram.puml
new file mode 100644
index 00000000000..565165c3df9
--- /dev/null
+++ b/docs/diagrams/EditTransactionActivityDiagram.puml
@@ -0,0 +1,21 @@
+@startuml
+start
+:User executes edit command;
+
+if () then ([user is not on Expenses or Incomes tab])
+ :Throw a ParseException;
+ stop
+else ([else])
+ if () then ([user is on Expenses tab])
+ :Use EditExpenseCommand;
+ :Edit target expense;
+ :Display updated expenses list;
+ else ([user is on Incomes tab])
+ :Use EditIncomeCommand;
+ :Edit target income;
+ :Display updated incomes list;
+ endif
+endif
+
+stop
+@enduml
diff --git a/docs/diagrams/EditTransactionClassDiagram.puml b/docs/diagrams/EditTransactionClassDiagram.puml
new file mode 100644
index 00000000000..8078dd3f543
--- /dev/null
+++ b/docs/diagrams/EditTransactionClassDiagram.puml
@@ -0,0 +1,63 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR_T4
+skinparam classBackgroundColor LOGIC_COLOR
+
+package Logic {
+
+package Parser {
+Class FinanceTrackerParser
+Class EditCommandParser
+}
+
+package Command {
+Class EditCommand
+Class EditExpenseCommand
+Class EditIncomeCommand
+Class EditTransactionDescriptor
+}
+}
+
+
+package Model {
+
+package Transaction {
+Class Expense MODEL_COLOR
+Class Income MODEL_COLOR
+Class "{abstract}\nTransaction" as Transaction MODEL_COLOR
+Class Title MODEL_COLOR
+Class Amount MODEL_COLOR
+Class Date MODEL_COLOR
+}
+package Category {
+Class Category MODEL_COLOR
+}
+}
+
+FinanceTrackerParser .> EditCommandParser: creates >
+EditCommandParser .> EditCommand: creates >
+
+FinanceTrackerParser ..> EditExpenseCommand: creates >
+FinanceTrackerParser ..> EditIncomeCommand: creates >
+
+EditExpenseCommand --|> EditCommand
+EditIncomeCommand --|> EditCommand
+EditCommand *-> "1" EditTransactionDescriptor
+EditCommand *----> "1" Transaction: modifies >
+
+note bottom on link: Reference is stored in\nthe form of an Index
+
+EditTransactionDescriptor --> "0..1" Title
+EditTransactionDescriptor --> "0..1" Amount
+EditTransactionDescriptor --> "0..1" Date
+EditTransactionDescriptor --> "*" Category
+
+Expense -up-|> Transaction MODEL_COLOR
+Income -up-|> Transaction MODEL_COLOR
+Transaction *-> "1" Title MODEL_COLOR
+Transaction *-> "1" Amount MODEL_COLOR
+Transaction *-> "1" Date MODEL_COLOR
+Transaction *-> "*" Category MODEL_COLOR
+
+@enduml
diff --git a/docs/diagrams/EditTransactionSequenceDiagram.puml b/docs/diagrams/EditTransactionSequenceDiagram.puml
new file mode 100644
index 00000000000..5a67d73002c
--- /dev/null
+++ b/docs/diagrams/EditTransactionSequenceDiagram.puml
@@ -0,0 +1,80 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":FinanceTrackerParser" as FinanceTrackerParser LOGIC_COLOR
+participant ":EditCommandParser" as EditCommandParser LOGIC_COLOR
+participant ":EditTransactionDescriptor" as EditTransactionDescriptor LOGIC_COLOR
+participant ":EditCommand" as EditCommand LOGIC_COLOR
+participant "cmd:EditExpenseCommand" as EditExpenseCommand LOGIC_COLOR
+end box
+
+[-> LogicManager : execute("edit 1 a/5", uiState)
+activate LogicManager
+
+LogicManager -> FinanceTrackerParser : parseCommand("edit 1 a/5", uiState)
+activate FinanceTrackerParser
+
+create EditCommandParser
+FinanceTrackerParser -> EditCommandParser
+activate EditCommandParser
+
+EditCommandParser --> FinanceTrackerParser
+deactivate EditCommandParser
+
+FinanceTrackerParser -> EditCommandParser : parse("...")
+activate EditCommandParser
+
+ref over EditCommandParser: Parse user input
+
+' TODO parsing data fields takes place here
+
+create EditTransactionDescriptor
+EditCommandParser -> EditTransactionDescriptor
+activate EditTransactionDescriptor
+EditTransactionDescriptor --> EditCommandParser
+deactivate EditTransactionDescriptor
+
+create EditCommand
+EditCommandParser -> EditCommand
+activate EditCommand
+
+EditCommand --> EditCommandParser
+deactivate EditCommand
+
+EditCommandParser --> FinanceTrackerParser
+
+deactivate EditCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+EditCommandParser -[hidden]-> FinanceTrackerParser
+destroy EditCommandParser
+
+create EditExpenseCommand
+FinanceTrackerParser -> EditExpenseCommand
+activate EditExpenseCommand
+
+EditExpenseCommand -> EditCommand: getTargetIndex()
+activate EditCommand
+EditCommand --> EditExpenseCommand
+deactivate EditCommand
+
+EditExpenseCommand -> EditCommand: getEditTransactionDescriptor()
+activate EditCommand
+EditCommand --> EditExpenseCommand
+deactivate EditCommand
+
+EditCommand -[hidden]-> FinanceTrackerParser
+destroy EditCommand
+
+EditExpenseCommand --> FinanceTrackerParser
+deactivate EditExpenseCommand
+
+FinanceTrackerParser --> LogicManager : cmd
+deactivate FinanceTrackerParser
+
+ref over LogicManager: Execute edit command
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/EditTransactionSequenceDiagram2.puml b/docs/diagrams/EditTransactionSequenceDiagram2.puml
new file mode 100644
index 00000000000..4c9c1c8f686
--- /dev/null
+++ b/docs/diagrams/EditTransactionSequenceDiagram2.puml
@@ -0,0 +1,87 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":EditTransactionDescriptor" as EditTransactionDescriptor LOGIC_COLOR
+participant "cmd:EditExpenseCommand" as EditExpenseCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "edited:Expense" as Expense MODEL_COLOR
+participant ":ModelManager" as ModelManager MODEL_COLOR
+end box
+
+[-> LogicManager : execute("edit 1 a/5", uiState)
+activate LogicManager
+
+ref over LogicManager, EditTransactionDescriptor, EditExpenseCommand: Parse command
+
+
+LogicManager -> EditExpenseCommand : execute(model)
+activate EditExpenseCommand
+
+EditExpenseCommand -> ModelManager : getFilteredExpenseList()
+activate ModelManager
+
+ModelManager --> EditExpenseCommand
+deactivate ModelManager
+
+EditExpenseCommand -> EditExpenseCommand: createEditedExpense(descriptor)
+activate EditExpenseCommand
+
+EditExpenseCommand -> EditTransactionDescriptor: descriptor.getTitle()
+activate EditTransactionDescriptor
+
+EditTransactionDescriptor --> EditExpenseCommand: title
+deactivate EditTransactionDescriptor
+
+EditExpenseCommand -> EditTransactionDescriptor: descriptor.getAmount()
+activate EditTransactionDescriptor
+
+EditTransactionDescriptor --> EditExpenseCommand: amount
+deactivate EditTransactionDescriptor
+
+EditExpenseCommand -> EditTransactionDescriptor: descriptor.getDate()
+activate EditTransactionDescriptor
+
+EditTransactionDescriptor --> EditExpenseCommand: date
+deactivate EditTransactionDescriptor
+
+EditExpenseCommand -> EditTransactionDescriptor: descriptor.getCategories()
+activate EditTransactionDescriptor
+
+EditTransactionDescriptor --> EditExpenseCommand: categories
+deactivate EditTransactionDescriptor
+
+
+create Expense
+EditExpenseCommand -> Expense
+activate Expense
+
+Expense --> EditExpenseCommand
+deactivate Expense
+
+EditExpenseCommand --> EditExpenseCommand: edited
+deactivate EditExpenseCommand
+
+EditExpenseCommand -> ModelManager : setTransaction(toEdit, edited)
+activate ModelManager
+
+ModelManager --> EditExpenseCommand
+deactivate ModelManager
+
+create CommandResult
+EditExpenseCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> EditExpenseCommand
+deactivate CommandResult
+
+EditExpenseCommand --> LogicManager : result
+deactivate EditExpenseCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/FindActivityDiagram.puml b/docs/diagrams/FindActivityDiagram.puml
new file mode 100644
index 00000000000..63c95200a08
--- /dev/null
+++ b/docs/diagrams/FindActivityDiagram.puml
@@ -0,0 +1,14 @@
+@startuml
+start
+:User executes find command;
+
+if () then ([user is on Overview tab])
+ :Filters all transactions;
+elseif () then ([user is on Expenses tab])
+ :Filters all expenses;
+else ([user is on Incomes tab])
+ :Filters all incomes;
+endif
+ :Displays filtered list on current tab;
+stop
+@enduml
diff --git a/docs/diagrams/FindClassDiagram.puml b/docs/diagrams/FindClassDiagram.puml
new file mode 100644
index 00000000000..ece242c67f0
--- /dev/null
+++ b/docs/diagrams/FindClassDiagram.puml
@@ -0,0 +1,39 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR_T4
+skinparam classBackgroundColor LOGIC_COLOR
+
+package Logic {
+
+package Parser {
+Class FinanceTrackerParser
+Class FindCommandParser
+}
+
+package Command {
+Class FindCommand
+Class FindXYZCommand
+}
+}
+
+package Model{
+package Predicate {
+Class HiddenModel #FFFFFF
+}
+}
+
+Class HiddenOutside #FFFFFF
+
+FinanceTrackerParser ..> FindCommandParser: creates >
+FinanceTrackerParser ..> FindXYZCommand: creates >
+
+FindCommandParser ..> FindCommand : creates >
+FindCommandParser ..> Predicate : creates >
+FindXYZCommand -up-|> FindCommand
+
+FindCommand o--> "*" Predicate
+
+note left of FindXYZCommand: FindXYZCommand = FindExpenseCommand, \nFindIncomeCommand, FindTransactionCommand
+
+@enduml
diff --git a/docs/diagrams/FindSequenceDiagram.puml b/docs/diagrams/FindSequenceDiagram.puml
new file mode 100644
index 00000000000..f9c51f307f3
--- /dev/null
+++ b/docs/diagrams/FindSequenceDiagram.puml
@@ -0,0 +1,78 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":FinanceTrackerParser" as FinanceTrackerParser LOGIC_COLOR
+participant ":FindCommandParser" as FindCommandParser LOGIC_COLOR
+participant "b:FindCommand" as FindCommand LOGIC_COLOR
+participant "f:FindTransactionCommand" as FindTransactionCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":ModelManager" as ModelManager MODEL_COLOR
+end box
+
+[-> LogicManager : execute("find t/tea a/5", uiState)
+activate LogicManager
+
+LogicManager -> FinanceTrackerParser : parseCommand("find t/tea a/5", uiState)
+activate FinanceTrackerParser
+
+create FindCommandParser
+FinanceTrackerParser -> FindCommandParser
+activate FindCommandParser
+
+FindCommandParser --> FinanceTrackerParser
+deactivate FindCommandParser
+
+FinanceTrackerParser -> FindCommandParser : parse(" t/tea a/5")
+activate FindCommandParser
+
+create FindCommand
+FindCommandParser -> FindCommand
+activate FindCommand
+
+FindCommand --> FindCommandParser : b
+deactivate FindCommand
+
+FindCommandParser --> FinanceTrackerParser : b
+
+deactivate FindCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+FindCommandParser -[hidden]-> FinanceTrackerParser
+destroy FindCommandParser
+
+create FindTransactionCommand
+FinanceTrackerParser -> FindTransactionCommand : FindTransactionCommand(b)
+activate FindTransactionCommand
+
+FindTransactionCommand --> FinanceTrackerParser : f
+deactivate FindTransactionCommand
+
+FinanceTrackerParser --> LogicManager : f
+deactivate FinanceTrackerParser
+
+LogicManager -> FindTransactionCommand : execute()
+activate FindTransactionCommand
+
+FindTransactionCommand -> ModelManager : updateFilteredTransactionList(f.getPredicates())
+activate ModelManager
+
+ModelManager --> FindTransactionCommand
+deactivate ModelManager
+
+create CommandResult
+FindTransactionCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> FindTransactionCommand
+deactivate CommandResult
+
+FindTransactionCommand --> LogicManager : result
+deactivate FindTransactionCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml
index 016ef33e2e2..9f17e4ae553 100644
--- a/docs/diagrams/LogicClassDiagram.puml
+++ b/docs/diagrams/LogicClassDiagram.puml
@@ -5,10 +5,11 @@ skinparam arrowColor LOGIC_COLOR_T4
skinparam classBackgroundColor LOGIC_COLOR
package Logic {
+class Timekeeper
package Parser {
Interface Parser <>
-Class AddressBookParser
+Class FinanceTrackerParser
Class XYZCommandParser
Class CliSyntax
Class ParserUtil
@@ -35,8 +36,9 @@ Class HiddenOutside #FFFFFF
HiddenOutside ..> Logic
LogicManager .up.|> Logic
-LogicManager -->"1" AddressBookParser
-AddressBookParser .left.> XYZCommandParser: creates >
+LogicManager -->"1" FinanceTrackerParser
+LogicManager -left-> "1" Timekeeper
+FinanceTrackerParser .left.> XYZCommandParser: creates >
XYZCommandParser ..> XYZCommand : creates >
XYZCommandParser ..|> Parser
@@ -53,7 +55,7 @@ LogicManager .left.> Command : executes >
LogicManager --> Model
Command .right.> Model
-note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc
+note right of XYZCommand: XYZCommand = AddExpenseCommand, \nFindCommand, etc
Logic ..> CommandResult
LogicManager .down.> CommandResult
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index e85a00d4107..e327e18af14 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -5,52 +5,70 @@ skinparam arrowColor MODEL_COLOR
skinparam classBackgroundColor MODEL_COLOR
Package Model <>{
-Interface ReadOnlyAddressBook <>
+Interface ReadOnlyFinanceTracker <>
Interface Model <>
-Interface ObservableList <>
-Class AddressBook
-Class ReadOnlyAddressBook
+Interface ReadOnlyUserPrefs <>
+Class FinanceTracker
Class Model
Class ModelManager
Class UserPrefs
-Class ReadOnlyUserPrefs
-
-Package Person {
-Class Person
-Class Address
-Class Email
-Class Name
-Class Phone
-Class UniquePersonList
+class CommandHistory
+
+Package Transaction {
+Class "{abstract}\nTransaction" as Transaction
+Class Expense
+Class Income
+Class Amount
+Class Date
+Class Title
+Class TransactionList
+}
+
+Package Category {
+Class Category
+}
+
+Package Budget {
+Class MonthlyBudget
}
-Package Tag {
-Class Tag
+Package Bookmark {
+Class BookmarkExpenseList
+Class BookmarkIncomeList
}
}
Class HiddenOutside #FFFFFF
HiddenOutside ..> Model
-AddressBook .up.|> ReadOnlyAddressBook
+FinanceTracker .up.|> ReadOnlyFinanceTracker
ModelManager .up.|> Model
-Model .right.> ObservableList
-ModelManager o--> "1" AddressBook
+ModelManager o--> "1" FinanceTracker
+ModelManager o-left-> "1" CommandHistory
ModelManager o-left-> "1" UserPrefs
UserPrefs .up.|> ReadOnlyUserPrefs
-AddressBook *--> "1" UniquePersonList
-UniquePersonList o--> "*" Person
-Person *--> Name
-Person *--> Phone
-Person *--> Email
-Person *--> Address
-Person *--> "*" Tag
+FinanceTracker *--> "1" TransactionList
+TransactionList o--> "*" Transaction
+Expense -left-|> Transaction
+Income -right-|> Transaction
+Transaction *--> "1" Title
+Transaction *--> "1" Amount
+Transaction *--> "1" Date
+Transaction *--> "*" Category
+
+FinanceTracker *--> "1" BookmarkExpenseList
+FinanceTracker *--> "1" BookmarkIncomeList
+
+FinanceTracker *--> "1" MonthlyBudget
+
+TransactionList -[hidden]right-> BookmarkExpenseList
+BookmarkIncomeList -[hidden]down-> BookmarkExpenseList
+BookmarkExpenseList -[hidden]down-> MonthlyBudget
+ModelManager -[hidden]right-> FinanceTracker
-Name -[hidden]right-> Phone
-Phone -[hidden]right-> Address
-Address -[hidden]right-> Email
+ModelManager --> "*" Transaction : filtered list
+ModelManager o--> "1" MonthlyBudget
-ModelManager -->"1" Person : filtered list
@enduml
diff --git a/docs/diagrams/SetExpenseLimitSequenceDiagram.puml b/docs/diagrams/SetExpenseLimitSequenceDiagram.puml
new file mode 100644
index 00000000000..e4941df0f3e
--- /dev/null
+++ b/docs/diagrams/SetExpenseLimitSequenceDiagram.puml
@@ -0,0 +1,69 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":FinanceTrackerParser" as FinanceTrackerParser LOGIC_COLOR
+participant ":SetExpenseLimitCommandParser" as SetExpenseLimitCommandParser LOGIC_COLOR
+participant "s:SetExpenseLimitCommand" as SetExpenseLimitCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":ModelManager" as ModelManager MODEL_COLOR
+end box
+
+[-> LogicManager : execute("setel a/500")
+activate LogicManager
+
+LogicManager -> FinanceTrackerParser : parseCommand("setel a/500")
+activate FinanceTrackerParser
+
+create SetExpenseLimitCommandParser
+FinanceTrackerParser -> SetExpenseLimitCommandParser
+activate SetExpenseLimitCommandParser
+
+SetExpenseLimitCommandParser --> FinanceTrackerParser
+deactivate SetExpenseLimitCommandParser
+
+FinanceTrackerParser -> SetExpenseLimitCommandParser : parse("a/500")
+activate SetExpenseLimitCommandParser
+
+create SetExpenseLimitCommand
+SetExpenseLimitCommandParser -> SetExpenseLimitCommand : SetExpenseLimitCommand(amount)
+activate SetExpenseLimitCommand
+
+SetExpenseLimitCommand --> SetExpenseLimitCommandParser : s
+deactivate SetExpenseLimitCommand
+
+SetExpenseLimitCommandParser --> FinanceTrackerParser : s
+deactivate SetExpenseLimitCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+SetExpenseLimitCommandParser -[hidden]-> FinanceTrackerParser
+destroy SetExpenseLimitCommandParser
+
+FinanceTrackerParser --> LogicManager : s
+deactivate FinanceTrackerParser
+
+LogicManager -> SetExpenseLimitCommand : execute()
+activate SetExpenseLimitCommand
+
+SetExpenseLimitCommand -> ModelManager : setExpenseLimit(amount)
+activate ModelManager
+
+ModelManager --> SetExpenseLimitCommand
+deactivate ModelManager
+
+create CommandResult
+SetExpenseLimitCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> SetExpenseLimitCommand
+deactivate CommandResult
+
+SetExpenseLimitCommand --> LogicManager : result
+deactivate SetExpenseLimitCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index 6adb2e156bf..7d17654683a 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -6,19 +6,21 @@ skinparam classBackgroundColor STORAGE_COLOR
Interface Storage <>
Interface UserPrefsStorage <>
-Interface AddressBookStorage <>
+Interface FinanceTrackerStorage <>
Class StorageManager
Class JsonUserPrefsStorage
-Class JsonAddressBookStorage
+Class JsonFinanceTrackerStorage
StorageManager .left.|> Storage
StorageManager o-right-> UserPrefsStorage
-StorageManager o--> AddressBookStorage
+StorageManager o--> FinanceTrackerStorage
JsonUserPrefsStorage .left.|> UserPrefsStorage
-JsonAddressBookStorage .left.|> AddressBookStorage
-JsonAddressBookStorage .down.> JsonSerializableAddressBookStorage
-JsonSerializableAddressBookStorage .right.> JsonSerializablePerson
-JsonSerializablePerson .right.> JsonAdaptedTag
+JsonFinanceTrackerStorage .left.|> FinanceTrackerStorage
+JsonFinanceTrackerStorage .down.> JsonSerializableFinanceTracker
+JsonSerializableFinanceTracker --> "*" JsonAdaptedExpense
+JsonSerializableFinanceTracker --> "*" JsonAdaptedIncome
+JsonAdaptedExpense --> "*" JsonAdaptedCategory
+JsonAdaptedIncome --> "*" JsonAdaptedCategory
@enduml
diff --git a/docs/diagrams/TimekeeperSequenceDiagram.puml b/docs/diagrams/TimekeeperSequenceDiagram.puml
new file mode 100644
index 00000000000..27ea0ff5e48
--- /dev/null
+++ b/docs/diagrams/TimekeeperSequenceDiagram.puml
@@ -0,0 +1,48 @@
+@startuml
+!include style.puml
+
+actor User USER_COLOR
+
+box Ui UI_COLOR_T1
+participant ":MainWindow" as MainWindow UI_COLOR
+end box
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":Timekeeper" as Timekeeper LOGIC_COLOR
+participant "c:XYZCommand" as XYZCommand LOGIC_COLOR
+end box
+
+note right of XYZCommand : XYZCommand =\nAddExpenseCommand,\nFindCommand, etc
+
+User -> MainWindow : input command string
+activate MainWindow
+
+MainWindow -> LogicManager : execute()
+activate LogicManager
+
+LogicManager -> Timekeeper : checkIn()
+activate Timekeeper
+
+alt current system time is later or equals to the last observed time
+ ref over LogicManager, XYZCommand : Parse and execute user input
+
+ Timekeeper --> LogicManager : result
+
+ LogicManager --> MainWindow : result
+else else
+ Timekeeper --> LogicManager : TemporalException()
+ deactivate Timekeeper
+
+ LogicManager --> MainWindow : TemporalException()
+ deactivate Timekeeper
+
+ MainWindow -> MainWindow : disableApplication()
+ activate MainWindow
+
+ MainWindow --> MainWindow
+ deactivate MainWindow
+end
+MainWindow --> User
+deactivate MainWindow
+@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 92746f9fcf7..c764bda80b0 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -9,12 +9,36 @@ Interface Ui <>
Class "{abstract}\nUiPart" as UiPart
Class UiManager
Class MainWindow
-Class HelpWindow
Class ResultDisplay
-Class PersonListPanel
-Class PersonCard
-Class StatusBarFooter
+Class TransactionListPanel
+Class TransactionCard
Class CommandBox
+Class SavingsGoalPanel
+Class UiState
+Class DisabledWindow
+
+package Tabs {
+Class TwoColumnTabPane
+Class OverviewTabPane
+Class IncomeTabPane
+Class ExpenseTabPane
+Class AnalyticsTabPane
+Class UserGuideTabPane
+}
+
+package Expense {
+Class ExpensePanel
+}
+
+package Income {
+Class IncomePanel
+}
+
+package Bookmark {
+Class BookmarkIncomePanel
+Class BookmarkExpensePanel
+Class BookmarkTransactionCard
+}
}
package Model <> {
@@ -30,31 +54,68 @@ HiddenOutside ..> Ui
UiManager .left.|> Ui
UiManager -down-> MainWindow
-MainWindow --> HelpWindow
+UiManager --> UiState
+MainWindow --> UiState
MainWindow *-down-> CommandBox
MainWindow *-down-> ResultDisplay
-MainWindow *-down-> PersonListPanel
-MainWindow *-down-> StatusBarFooter
+MainWindow *-up-> DisabledWindow
-PersonListPanel -down-> PersonCard
+MainWindow *-down-> OverviewTabPane
+MainWindow *-down-> IncomeTabPane
+MainWindow *-down-> ExpenseTabPane
+MainWindow *-down-> AnalyticsTabPane
+MainWindow *-down-> UserGuideTabPane
+
+OverviewTabPane *-down-> TransactionListPanel
+OverviewTabPane *-down-> SavingsGoalPanel
+
+IncomeTabPane *-down-> IncomePanel
+IncomeTabPane *-down-> BookmarkIncomePanel
+
+ExpenseTabPane *-down-> ExpensePanel
+ExpenseTabPane *-down-> BookmarkExpensePanel
+
+TransactionListPanel -down-> TransactionCard
+ExpensePanel -down-> TransactionCard
+IncomePanel -down-> TransactionCard
+
+BookmarkExpensePanel -down-> BookmarkTransactionCard
+BookmarkIncomePanel -down-> BookmarkTransactionCard
MainWindow -left-|> UiPart
ResultDisplay --|> UiPart
CommandBox --|> UiPart
-PersonListPanel --|> UiPart
-PersonCard --|> UiPart
-StatusBarFooter --|> UiPart
-HelpWindow -down-|> UiPart
+TransactionListPanel --|> UiPart
+TransactionCard --|> UiPart
+ExpensePanel --|> UiPart
+IncomePanel --|> UiPart
+SavingsGoalPanel --|> UiPart
+BookmarkExpensePanel --|> UiPart
+BookmarkIncomePanel --|> UiPart
+BookmarkTransactionCard --|> UiPart
+
+TwoColumnTabPane --|> UiPart
+OverviewTabPane --|> TwoColumnTabPane
+IncomeTabPane --|> TwoColumnTabPane
+ExpenseTabPane --|> TwoColumnTabPane
+
+AnalyticsTabPane --|> UiPart
+UserGuideTabPane --|> UiPart
-PersonCard ..> Model
-UiManager -right-> Logic
-MainWindow -left-> Logic
+TransactionCard ..> Model
+UiManager -up-> Logic
+MainWindow -up-> Logic
-PersonListPanel -[hidden]left- HelpWindow
-HelpWindow -[hidden]left- CommandBox
CommandBox -[hidden]left- ResultDisplay
-ResultDisplay -[hidden]left- StatusBarFooter
+ExpensePanel -[hidden]left- TransactionListPanel
+IncomePanel -[hidden]left- SavingsGoalPanel
MainWindow -[hidden]-|> UiPart
+AnalyticsTabPane -[hidden]-|> OverviewTabPane
+UserGuideTabPane -[hidden]-|> OverviewTabPane
+TwoColumnTabPane -[hidden]down-|> SavingsGoalPanel
+UiPart -[hidden]down-|> Model
+Logic -[hidden]down-|> UiManager
+TwoColumnTabPane -[hidden]down-|> TransactionListPanel
@enduml
diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml
deleted file mode 100644
index 96e30744d24..00000000000
--- a/docs/diagrams/UndoRedoState0.puml
+++ /dev/null
@@ -1,20 +0,0 @@
-@startuml
-!include style.puml
-skinparam ClassFontColor #000000
-skinparam ClassBorderColor #000000
-
-title Initial state
-
-package States {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab2:AddressBook__"
-}
-State1 -[hidden]right-> State2
-State2 -[hidden]right-> State3
-hide State2
-hide State3
-
-class Pointer as "Current State" #FFFFF
-Pointer -up-> State1
-@end
diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml
deleted file mode 100644
index 01fcb9b2b96..00000000000
--- a/docs/diagrams/UndoRedoState1.puml
+++ /dev/null
@@ -1,22 +0,0 @@
-@startuml
-!include style.puml
-skinparam ClassFontColor #000000
-skinparam ClassBorderColor #000000
-
-title After command "delete 5"
-
-package States <> {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab2:AddressBook__"
-}
-
-State1 -[hidden]right-> State2
-State2 -[hidden]right-> State3
-
-hide State3
-
-class Pointer as "Current State" #FFFFF
-
-Pointer -up-> State2
-@end
diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml
deleted file mode 100644
index bccc230a5d1..00000000000
--- a/docs/diagrams/UndoRedoState2.puml
+++ /dev/null
@@ -1,20 +0,0 @@
-@startuml
-!include style.puml
-skinparam ClassFontColor #000000
-skinparam ClassBorderColor #000000
-
-title After command "add n/David"
-
-package States <> {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab2:AddressBook__"
-}
-
-State1 -[hidden]right-> State2
-State2 -[hidden]right-> State3
-
-class Pointer as "Current State" #FFFFF
-
-Pointer -up-> State3
-@end
diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml
deleted file mode 100644
index ea29c9483e4..00000000000
--- a/docs/diagrams/UndoRedoState3.puml
+++ /dev/null
@@ -1,20 +0,0 @@
-@startuml
-!include style.puml
-skinparam ClassFontColor #000000
-skinparam ClassBorderColor #000000
-
-title After command "undo"
-
-package States <> {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab2:AddressBook__"
-}
-
-State1 -[hidden]right-> State2
-State2 -[hidden]right-> State3
-
-class Pointer as "Current State" #FFFFF
-
-Pointer -up-> State2
-@end
diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml
deleted file mode 100644
index 1b784cece80..00000000000
--- a/docs/diagrams/UndoRedoState4.puml
+++ /dev/null
@@ -1,20 +0,0 @@
-@startuml
-!include style.puml
-skinparam ClassFontColor #000000
-skinparam ClassBorderColor #000000
-
-title After command "list"
-
-package States <> {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab2:AddressBook__"
-}
-
-State1 -[hidden]right-> State2
-State2 -[hidden]right-> State3
-
-class Pointer as "Current State" #FFFFF
-
-Pointer -up-> State2
-@end
diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml
deleted file mode 100644
index 88927be32bc..00000000000
--- a/docs/diagrams/UndoRedoState5.puml
+++ /dev/null
@@ -1,21 +0,0 @@
-@startuml
-!include style.puml
-skinparam ClassFontColor #000000
-skinparam ClassBorderColor #000000
-
-title After command "clear"
-
-package States <> {
- class State1 as "__ab0:AddressBook__"
- class State2 as "__ab1:AddressBook__"
- class State3 as "__ab3:AddressBook__"
-}
-
-State1 -[hidden]right-> State2
-State2 -[hidden]right-> State3
-
-class Pointer as "Current State" #FFFFF
-
-Pointer -up-> State3
-note right on link: State ab2 deleted.
-@end
diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/UndoSequenceDiagram.puml
deleted file mode 100644
index 410aab4e412..00000000000
--- a/docs/diagrams/UndoSequenceDiagram.puml
+++ /dev/null
@@ -1,53 +0,0 @@
-@startuml
-!include style.puml
-
-box Logic LOGIC_COLOR_T1
-participant ":LogicManager" as LogicManager LOGIC_COLOR
-participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
-participant "u:UndoCommand" as UndoCommand LOGIC_COLOR
-end box
-
-box Model MODEL_COLOR_T1
-participant ":Model" as Model MODEL_COLOR
-participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR
-end box
-[-> LogicManager : execute(undo)
-activate LogicManager
-
-LogicManager -> AddressBookParser : parseCommand(undo)
-activate AddressBookParser
-
-create UndoCommand
-AddressBookParser -> UndoCommand
-activate UndoCommand
-
-UndoCommand --> AddressBookParser
-deactivate UndoCommand
-
-AddressBookParser --> LogicManager : u
-deactivate AddressBookParser
-
-LogicManager -> UndoCommand : execute()
-activate UndoCommand
-
-UndoCommand -> Model : undoAddressBook()
-activate Model
-
-Model -> VersionedAddressBook : undo()
-activate VersionedAddressBook
-
-VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook)
-VersionedAddressBook --> Model :
-deactivate VersionedAddressBook
-
-Model --> UndoCommand
-deactivate Model
-
-UndoCommand --> LogicManager : result
-deactivate UndoCommand
-UndoCommand -[hidden]-> LogicManager : result
-destroy UndoCommand
-
-[<--LogicManager
-deactivate LogicManager
-@enduml
diff --git a/docs/diagrams/bookmarkdiagrams/AddBookmarkExpenseSequenceDiagram.puml b/docs/diagrams/bookmarkdiagrams/AddBookmarkExpenseSequenceDiagram.puml
new file mode 100644
index 00000000000..de8e03850ea
--- /dev/null
+++ b/docs/diagrams/bookmarkdiagrams/AddBookmarkExpenseSequenceDiagram.puml
@@ -0,0 +1,59 @@
+@startuml
+!include ../style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddBookmarkExpenseCommand" as AddBookmarkExpenseCommand LOGIC_COLOR
+participant "e:CommandException" as CommandException LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":ModelManager" as ModelManager MODEL_COLOR
+end box
+
+[->LogicManager: execute(command)
+activate LogicManager
+
+ref over LogicManager : parse user input to create AddBookmarkExpenseCommand
+
+create AddBookmarkExpenseCommand
+LogicManager -> AddBookmarkExpenseCommand: execute(model)
+activate AddBookmarkExpenseCommand
+
+AddBookmarkExpenseCommand -> ModelManager: addBookmarkExpense(toAdd)
+activate ModelManager
+
+alt #transparent duplicate BookmarkExpense
+
+ModelManager --> AddBookmarkExpenseCommand
+
+create CommandException
+AddBookmarkExpenseCommand -> CommandException
+activate CommandException
+
+CommandException --> LogicManager: e
+
+[<-- LogicManager: e
+deactivate CommandException
+
+else
+
+ModelManager --> AddBookmarkExpenseCommand
+deactivate ModelManager
+
+create CommandResult
+AddBookmarkExpenseCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> AddBookmarkExpenseCommand: result
+deactivate CommandResult
+
+AddBookmarkExpenseCommand --> LogicManager: result
+deactivate AddBookmarkExpenseCommand
+
+[<-- LogicManager: result
+deactivate LogicManager
+end
+
+@enduml
diff --git a/docs/diagrams/bookmarkdiagrams/AddBookmarkTransactionClassDiagram.puml b/docs/diagrams/bookmarkdiagrams/AddBookmarkTransactionClassDiagram.puml
new file mode 100644
index 00000000000..909e9154440
--- /dev/null
+++ b/docs/diagrams/bookmarkdiagrams/AddBookmarkTransactionClassDiagram.puml
@@ -0,0 +1,53 @@
+@startuml
+!include ../style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR_T4
+skinparam classBackgroundColor LOGIC_COLOR
+
+package Logic {
+
+package Parser {
+Class FinanceTrackerParser
+Class AddBookmarkExpenseCommandParser
+Class AddBookmarkIncomeCommandParser
+}
+
+package Command {
+Class AddBookmarkExpenseCommand
+Class AddBookmarkIncomeCommand
+}
+}
+
+
+package Model {
+
+package BookmarkTransaction {
+Class BookmarkExpense MODEL_COLOR
+Class BookmarkIncome MODEL_COLOR
+Class "{abstract}\nBookmarkTransaction" as BookmarkTransaction MODEL_COLOR
+Class Title MODEL_COLOR
+Class Amount MODEL_COLOR
+}
+package Category {
+Class Category MODEL_COLOR
+}
+}
+
+FinanceTrackerParser ..> AddBookmarkExpenseCommandParser: creates >
+FinanceTrackerParser ..> AddBookmarkIncomeCommandParser: creates >
+
+AddBookmarkExpenseCommandParser ..> AddBookmarkExpenseCommand: creates >
+AddBookmarkExpenseCommandParser ..> BookmarkExpense: creates >
+AddBookmarkIncomeCommandParser ..> AddBookmarkIncomeCommand: creates >
+AddBookmarkIncomeCommandParser ..> BookmarkIncome: creates >
+
+AddBookmarkExpenseCommand --> "1" BookmarkExpense
+AddBookmarkIncomeCommand --> "1" BookmarkIncome
+
+BookmarkExpense --|> BookmarkTransaction MODEL_COLOR
+BookmarkIncome --|> BookmarkTransaction MODEL_COLOR
+BookmarkTransaction *--> "1" Amount MODEL_COLOR
+BookmarkTransaction *--> "1" Title MODEL_COLOR
+BookmarkTransaction *--> "*" Category MODEL_COLOR
+
+@enduml
diff --git a/docs/diagrams/bookmarkdiagrams/BookmarkTransactionClassDiagram.puml b/docs/diagrams/bookmarkdiagrams/BookmarkTransactionClassDiagram.puml
new file mode 100644
index 00000000000..def3358c944
--- /dev/null
+++ b/docs/diagrams/bookmarkdiagrams/BookmarkTransactionClassDiagram.puml
@@ -0,0 +1,43 @@
+@startuml
+
+!include ../style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+Package Model{
+Class ModelManager
+Class FinanceTracker
+
+Package BookmarkTransaction {
+Class "{abstract}\nBookmarkTransaction" as BookmarkTransaction
+Class BookmarkExpenseList
+Class BookmarkIncomeList
+Class BookmarkExpense
+Class BookmarkIncome
+}
+
+Package Transaction {
+Class Transaction
+Class Title
+Class Amount
+}
+
+Package Category{
+Class Category
+}
+}
+ModelManager o--> FinanceTracker
+FinanceTracker *--> BookmarkExpenseList
+FinanceTracker *--> BookmarkIncomeList
+BookmarkExpenseList o--> "*" BookmarkExpense
+BookmarkIncomeList o--> "*" BookmarkIncome
+BookmarkExpense --|> BookmarkTransaction
+BookmarkIncome --|> BookmarkTransaction
+
+BookmarkTransaction ..> Transaction
+BookmarkTransaction *--> "1" Title
+BookmarkTransaction *--> "1" Amount
+BookmarkTransaction *--> "*" Category
+
+@enduml
diff --git a/docs/diagrams/bookmarkdiagrams/BookmarkTransactionCommandClassDiagram.puml b/docs/diagrams/bookmarkdiagrams/BookmarkTransactionCommandClassDiagram.puml
new file mode 100644
index 00000000000..31af7e2ebe0
--- /dev/null
+++ b/docs/diagrams/bookmarkdiagrams/BookmarkTransactionCommandClassDiagram.puml
@@ -0,0 +1,75 @@
+@startuml
+skinparam classAttributeIconSize 0
+
+class "{abstract}\nCommand" as Command{
++ execute(model: Model): CommandResult
+}
+class AddBookmarkExpenseCommand {
+{static} + COMMAND_WORD: String
+{static} + COMMAND_ALIAS: String
+- toAdd: BookmarkExpense
++ execute(model: Model): CommandResult
+}
+class AddBookmarkIncomeCommand {
+{static} + COMMAND_WORD: String
+{static} + COMMAND_ALIAS: String
+- toAdd: BookmarkIncome
++ execute(model: Model): CommandResult
+}
+class ConvertBookmarkCommand {
+{static} + COMMAND_WORD: String
+{static} + COMMAND_ALIAS: String
+- targetIndex: Index
+- convertedDate: Date
+}
+'class ConvertBookmarkExpenseCommand {
+'{static} + MESSAGE_CONVERT_BOOKMARK_EXPENSE_SUCCESS: String
+'+ execute(model: Model): CommandResult
+'}
+'class ConvertBookmarkIncomeCommand {
+'{static} + MESSAGE_CONVERT_BOOKMARK_INCOME_SUCCESS: String
+'+ execute(model: Model): CommandResult
+'}
+class DeleteBookmarkCommand {
+{static} + COMMAND_WORD: String
+{static} + MESSAGE_USAGE: String
+- targetIndex: Index
+}
+'class DeleteBookmarkExpenseCommand {
+'{static} + MESSAGE_DELETE_BOOKMARK_EXPENSE_SUCCESS: String
+'+ execute(model: Model): CommandResult
+'}
+'class DeleteBookmarkIncomeCommand {
+'{static} + MESSAGE_DELETE_BOOKMARK_INCOME_SUCCESS: String
+'+ execute(model: Model): CommandResult
+'}
+class EditBookmarkCommand {
+{static} + COMMAND_WORD: String
+{static} + MESSAGE_NOT_EDITED: String
+- targetIndex: Index
+- editBookmarkTransactionDescriptor: EditBookmarkTransactionDescriptor
+}
+'class EditBookmarkExpenseCommand {
+'{static} + MESSAGE_EDIT_BOOKMARK_EXPENSE_SUCCESS: String
+'+ execute(model: Model): CommandResult
+'}
+'class EditBookmarkIncomeCommand {
+'{static} + MESSAGE_EDIT_BOOKMARK_INCOME_SUCCESS: String
+'+ execute(model: Model): CommandResult
+'}
+
+
+Command <|-- AddBookmarkExpenseCommand
+Command <|-- AddBookmarkIncomeCommand
+Command <|-- DeleteBookmarkCommand
+Command <|-- ConvertBookmarkCommand
+Command <|-- EditBookmarkCommand
+'DeleteBookmarkCommand <|-- DeleteBookmarkExpenseCommand
+'DeleteBookmarkCommand <|-- DeleteBookmarkIncomeCommand
+'ConvertBookmarkCommand <|-- ConvertBookmarkExpenseCommand
+'ConvertBookmarkCommand <|-- ConvertBookmarkIncomeCommand
+'EditBookmarkCommand <|-- EditBookmarkExpenseCommand
+'EditBookmarkCommand <|-- EditBookmarkIncomeCommand
+
+hide circle
+@enduml
diff --git a/docs/diagrams/bookmarkdiagrams/ConvertBookmarkTransactionClassDiagram.puml b/docs/diagrams/bookmarkdiagrams/ConvertBookmarkTransactionClassDiagram.puml
new file mode 100644
index 00000000000..12facc01b16
--- /dev/null
+++ b/docs/diagrams/bookmarkdiagrams/ConvertBookmarkTransactionClassDiagram.puml
@@ -0,0 +1,31 @@
+@startuml
+!include ../style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR_T4
+skinparam classBackgroundColor LOGIC_COLOR
+
+package Logic {
+
+package Parser {
+class FinanceTrackerParser
+class ConvertBookmarkCommandParser
+}
+
+package Command {
+class ConvertBookmarkCommand
+class ConvertBookmarkXYZCommand
+}
+}
+
+Class HiddenOutside #FFFFFF
+
+FinanceTrackerParser ..> ConvertBookmarkCommandParser: creates >
+FinanceTrackerParser ..> ConvertBookmarkXYZCommand: creates >
+
+ConvertBookmarkCommandParser ..> ConvertBookmarkCommand: creates >
+
+ConvertBookmarkXYZCommand -up-|> ConvertBookmarkCommand
+
+note left of ConvertBookmarkXYZCommand: ConvertBookmarkXYZCommand = ConvertBookmarkExpenseCommand, \nConvertBookmarkIncomeCommand
+
+@enduml
diff --git a/docs/diagrams/bookmarkdiagrams/DeleteBookmarkExpenseActivityDiagram.puml b/docs/diagrams/bookmarkdiagrams/DeleteBookmarkExpenseActivityDiagram.puml
new file mode 100644
index 00000000000..4a761eddd63
--- /dev/null
+++ b/docs/diagrams/bookmarkdiagrams/DeleteBookmarkExpenseActivityDiagram.puml
@@ -0,0 +1,16 @@
+@startuml
+start
+:User executes delete-bookmark on Expense Tab;
+:Command gets parsed by logic component;
+:DeleteBookmarkExpenseCommand gets executed;
+
+if () is ([Valid index provided]) then
+:Deletes specified bookmark expense;
+:Display delete bookmark expense success message;
+
+else ([else])
+:Display invalid index provided message;
+
+endif
+stop
+@enduml
diff --git a/docs/diagrams/bookmarkdiagrams/DeleteBookmarkTransactionClassDiagram.puml b/docs/diagrams/bookmarkdiagrams/DeleteBookmarkTransactionClassDiagram.puml
new file mode 100644
index 00000000000..168cff37b1f
--- /dev/null
+++ b/docs/diagrams/bookmarkdiagrams/DeleteBookmarkTransactionClassDiagram.puml
@@ -0,0 +1,31 @@
+@startuml
+!include ../style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR_T4
+skinparam classBackgroundColor LOGIC_COLOR
+
+package Logic {
+
+package Parser {
+class FinanceTrackerParser
+class DeleteBookmarkCommandParser
+}
+
+package Command {
+class DeleteBookmarkCommand
+class DeleteBookmarkXYZCommand
+}
+}
+
+Class HiddenOutside #FFFFFF
+
+FinanceTrackerParser ..> DeleteBookmarkCommandParser: creates >
+FinanceTrackerParser ..> DeleteBookmarkXYZCommand: creates >
+
+DeleteBookmarkCommandParser ..> DeleteBookmarkCommand: creates >
+
+DeleteBookmarkXYZCommand -up-|> DeleteBookmarkCommand
+
+note left of DeleteBookmarkXYZCommand: DeleteBookmarkXYZCommand = DeleteBookmarkExpenseCommand, \nDeleteBookmarkIncomeCommand
+
+@enduml
diff --git a/docs/diagrams/bookmarkdiagrams/EditBookmarkTransactionClassDiagram.puml b/docs/diagrams/bookmarkdiagrams/EditBookmarkTransactionClassDiagram.puml
new file mode 100644
index 00000000000..bd57d77942d
--- /dev/null
+++ b/docs/diagrams/bookmarkdiagrams/EditBookmarkTransactionClassDiagram.puml
@@ -0,0 +1,31 @@
+@startuml
+!include ../style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor LOGIC_COLOR_T4
+skinparam classBackgroundColor LOGIC_COLOR
+
+package Logic {
+
+package Parser {
+class FinanceTrackerParser
+class EditBookmarkCommandParser
+}
+
+package Command {
+class EditBookmarkCommand
+class EditBookmarkXYZCommand
+}
+}
+
+Class HiddenOutside #FFFFFF
+
+FinanceTrackerParser ..> EditBookmarkCommandParser: creates >
+FinanceTrackerParser ..> EditBookmarkXYZCommand: creates >
+
+EditBookmarkCommandParser ..> EditBookmarkCommand: creates >
+
+EditBookmarkXYZCommand -up-|> EditBookmarkCommand
+
+note left of EditBookmarkXYZCommand: EditBookmarkXYZCommand = EditBookmarkExpenseCommand, \nEditBookmarkIncomeCommand
+
+@enduml
diff --git a/docs/diagrams/bookmarkdiagrams/ParsingAddBookmarkExpenseInputSequenceDiagram.puml b/docs/diagrams/bookmarkdiagrams/ParsingAddBookmarkExpenseInputSequenceDiagram.puml
new file mode 100644
index 00000000000..e763d07d298
--- /dev/null
+++ b/docs/diagrams/bookmarkdiagrams/ParsingAddBookmarkExpenseInputSequenceDiagram.puml
@@ -0,0 +1,34 @@
+@startuml
+!include ../style.puml
+
+mainframe sd parse user input to create AddBookmarkExpenseCommand
+
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":FinanceTrackerParser" as FinanceTrackerParser LOGIC_COLOR
+participant ":AddBookmarkExpenseCommandParser" as AddBookmarkExpenseCommandParser LOGIC_COLOR
+participant ":AddBookmarkExpenseCommand" as AddBookmarkExpenseCommand LOGIC_COLOR
+
+activate LogicManager
+
+LogicManager -> FinanceTrackerParser: parseCommand(command, uiState)
+activate FinanceTrackerParser
+
+create AddBookmarkExpenseCommandParser
+FinanceTrackerParser -> AddBookmarkExpenseCommandParser: AddBookmarkExpenseCommandParser()
+activate AddBookmarkExpenseCommandParser
+
+AddBookmarkExpenseCommandParser --> FinanceTrackerParser
+deactivate AddBookmarkExpenseCommandParser
+
+create AddBookmarkExpenseCommand
+FinanceTrackerParser -> AddBookmarkExpenseCommand: parse(arguments)
+activate AddBookmarkExpenseCommand
+
+AddBookmarkExpenseCommand --> FinanceTrackerParser: command
+deactivate AddBookmarkExpenseCommand
+
+FinanceTrackerParser --> LogicManager: command
+destroy AddBookmarkExpenseCommandParser
+deactivate FinanceTrackerParser
+
+@enduml
diff --git a/docs/diagrams/bookmarkdiagrams/ParsingConvertBookmarkExpenseInputSequenceDiagram.puml b/docs/diagrams/bookmarkdiagrams/ParsingConvertBookmarkExpenseInputSequenceDiagram.puml
new file mode 100644
index 00000000000..93a386de75f
--- /dev/null
+++ b/docs/diagrams/bookmarkdiagrams/ParsingConvertBookmarkExpenseInputSequenceDiagram.puml
@@ -0,0 +1,45 @@
+@startuml
+!include ../style.puml
+
+mainframe sd parse user input to create ConvertBookmarkExpenseCommand
+
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":FinanceTrackerParser" as FinanceTrackerParser LOGIC_COLOR
+participant ":ConvertBookmarkCommandParser" as ConvertBookmarkCommandParser LOGIC_COLOR
+participant "cbc:ConvertBookmarkCommand" as ConvertBookmarkCommand LOGIC_COLOR
+participant "cbec:ConvertBookmarkExpenseCommand" as ConvertBookmarkExpenseCommand LOGIC_COLOR
+
+activate LogicManager
+
+LogicManager -> FinanceTrackerParser: parseCommand(command, uiState)
+activate FinanceTrackerParser
+
+create ConvertBookmarkCommandParser
+FinanceTrackerParser -> ConvertBookmarkCommandParser: ConvertBookmarkCommandParser()
+activate ConvertBookmarkCommandParser
+
+create ConvertBookmarkCommand
+ConvertBookmarkCommandParser -> ConvertBookmarkCommand: parse(arguments)
+activate ConvertBookmarkCommand
+deactivate ConvertBookmarkCommandParser
+
+ConvertBookmarkCommand --> FinanceTrackerParser: cbc
+deactivate ConvertBookmarkCommand
+
+ConvertBookmarkCommandParser -[hidden]-> FinanceTrackerParser
+destroy ConvertBookmarkCommandParser
+
+create ConvertBookmarkExpenseCommand
+FinanceTrackerParser -> ConvertBookmarkExpenseCommand: ConvertBookmarkExpenseCommand(convertBookmarkCommand)
+activate ConvertBookmarkExpenseCommand
+
+ConvertBookmarkExpenseCommand --> FinanceTrackerParser: cbec
+deactivate ConvertBookmarkExpenseCommand
+
+ConvertBookmarkCommand -[hidden]-> FinanceTrackerParser
+destroy ConvertBookmarkCommand
+
+FinanceTrackerParser --> LogicManager: cbec
+deactivate FinanceTrackerParser
+
+@enduml
diff --git a/docs/diagrams/style.puml b/docs/diagrams/style.puml
index fad8b0adeaa..e213092fa59 100644
--- a/docs/diagrams/style.puml
+++ b/docs/diagrams/style.puml
@@ -31,6 +31,9 @@
!define STORAGE_COLOR_T3 #806600
!define STORAGE_COLOR_T2 #544400
+!define BOOKMARK_TRANSACTION_COLOR #ff4500
+!define BOOKMARK_TRANSACTION__COLOR_T1 #ffc87c
+
!define USER_COLOR #000000
skinparam BackgroundColor #FFFFFFF
diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/tracing/LogicSequenceDiagram.puml
deleted file mode 100644
index fdcbe1c0ccc..00000000000
--- a/docs/diagrams/tracing/LogicSequenceDiagram.puml
+++ /dev/null
@@ -1,21 +0,0 @@
-@startuml
-!include ../style.puml
-
-Participant ":LogicManager" as logic LOGIC_COLOR
-Participant ":AddressBookParser" as abp LOGIC_COLOR
-Participant ":EditCommandParser" as ecp LOGIC_COLOR
-Participant "command:EditCommand" as ec LOGIC_COLOR
-
-[-> logic : execute
-activate logic
-logic -> abp ++: parseCommand(commandText)
-create ecp
-abp -> ecp
-abp -> ecp ++: parse(arguments)
-create ec
-ecp -> ec ++: index, editPersonDescriptor
-ec --> ecp --
-ecp --> abp --: command
-abp --> logic --: command
-
-@enduml
diff --git a/docs/images/AddBookmarkExpenseSequenceDiagram.png b/docs/images/AddBookmarkExpenseSequenceDiagram.png
new file mode 100644
index 00000000000..ce31386d0ed
Binary files /dev/null and b/docs/images/AddBookmarkExpenseSequenceDiagram.png differ
diff --git a/docs/images/AddBookmarkTransactionClassDiagram.png b/docs/images/AddBookmarkTransactionClassDiagram.png
new file mode 100644
index 00000000000..5dcbc301b73
Binary files /dev/null and b/docs/images/AddBookmarkTransactionClassDiagram.png differ
diff --git a/docs/images/AddTransactionActivityDiagram.png b/docs/images/AddTransactionActivityDiagram.png
new file mode 100644
index 00000000000..9ddb9cf4ff1
Binary files /dev/null and b/docs/images/AddTransactionActivityDiagram.png differ
diff --git a/docs/images/AddTransactionClassDiagram.png b/docs/images/AddTransactionClassDiagram.png
new file mode 100644
index 00000000000..989037f202d
Binary files /dev/null and b/docs/images/AddTransactionClassDiagram.png differ
diff --git a/docs/images/AddTransactionSequenceDiagram.png b/docs/images/AddTransactionSequenceDiagram.png
new file mode 100644
index 00000000000..47b0218362d
Binary files /dev/null and b/docs/images/AddTransactionSequenceDiagram.png differ
diff --git a/docs/images/AnalyticsTabObjectDiagram.png b/docs/images/AnalyticsTabObjectDiagram.png
new file mode 100644
index 00000000000..b7ac2820ae1
Binary files /dev/null and b/docs/images/AnalyticsTabObjectDiagram.png differ
diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png
index 2f1346869d0..d58c4e39bff 100644
Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ
diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png
deleted file mode 100644
index bc7ed18ae29..00000000000
Binary files a/docs/images/BetterModelClassDiagram.png and /dev/null differ
diff --git a/docs/images/BookmarkTransactionClassDiagram.png b/docs/images/BookmarkTransactionClassDiagram.png
new file mode 100644
index 00000000000..aca6eb9e3a4
Binary files /dev/null and b/docs/images/BookmarkTransactionClassDiagram.png differ
diff --git a/docs/images/BookmarkTransactionCommandClassDiagram.png b/docs/images/BookmarkTransactionCommandClassDiagram.png
new file mode 100644
index 00000000000..5c9018abe49
Binary files /dev/null and b/docs/images/BookmarkTransactionCommandClassDiagram.png differ
diff --git a/docs/images/BudgetActivityDiagram.png b/docs/images/BudgetActivityDiagram.png
new file mode 100644
index 00000000000..021be458377
Binary files /dev/null and b/docs/images/BudgetActivityDiagram.png differ
diff --git a/docs/images/BudgetClassDiagram.png b/docs/images/BudgetClassDiagram.png
new file mode 100644
index 00000000000..9fc5a28e150
Binary files /dev/null and b/docs/images/BudgetClassDiagram.png differ
diff --git a/docs/images/CommandHistoryClassDiagram.png b/docs/images/CommandHistoryClassDiagram.png
new file mode 100644
index 00000000000..b1cb013d47b
Binary files /dev/null and b/docs/images/CommandHistoryClassDiagram.png differ
diff --git a/docs/images/CommandHistoryDownActivityDiagram.png b/docs/images/CommandHistoryDownActivityDiagram.png
new file mode 100644
index 00000000000..c50826db8c5
Binary files /dev/null and b/docs/images/CommandHistoryDownActivityDiagram.png differ
diff --git a/docs/images/CommandHistoryUpActivityDiagram.png b/docs/images/CommandHistoryUpActivityDiagram.png
new file mode 100644
index 00000000000..0c5b27ce893
Binary files /dev/null and b/docs/images/CommandHistoryUpActivityDiagram.png differ
diff --git a/docs/images/CommandResultSequenceDiagram.png b/docs/images/CommandResultSequenceDiagram.png
new file mode 100644
index 00000000000..32b09fee581
Binary files /dev/null and b/docs/images/CommandResultSequenceDiagram.png differ
diff --git a/docs/images/CommitActivityDiagram.png b/docs/images/CommitActivityDiagram.png
deleted file mode 100644
index 4de4fa4bf2b..00000000000
Binary files a/docs/images/CommitActivityDiagram.png and /dev/null differ
diff --git a/docs/images/ConvertBookmarkExpenseSequenceDiagram.png b/docs/images/ConvertBookmarkExpenseSequenceDiagram.png
new file mode 100644
index 00000000000..c6ea447bfc6
Binary files /dev/null and b/docs/images/ConvertBookmarkExpenseSequenceDiagram.png differ
diff --git a/docs/images/ConvertBookmarkTransactionClassDiagram.png b/docs/images/ConvertBookmarkTransactionClassDiagram.png
new file mode 100644
index 00000000000..f4903f7110e
Binary files /dev/null and b/docs/images/ConvertBookmarkTransactionClassDiagram.png differ
diff --git a/docs/images/DeleteBookmarkExpenseActivityDiagram.png b/docs/images/DeleteBookmarkExpenseActivityDiagram.png
new file mode 100644
index 00000000000..756f5cb823e
Binary files /dev/null and b/docs/images/DeleteBookmarkExpenseActivityDiagram.png differ
diff --git a/docs/images/DeleteBookmarkTransactionClassDiagram.png b/docs/images/DeleteBookmarkTransactionClassDiagram.png
new file mode 100644
index 00000000000..229c0875c2d
Binary files /dev/null and b/docs/images/DeleteBookmarkTransactionClassDiagram.png differ
diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png
index fa327b39618..6e223b0ed03 100644
Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ
diff --git a/docs/images/DeleteTransactionActivityDiagram.png b/docs/images/DeleteTransactionActivityDiagram.png
new file mode 100644
index 00000000000..4cdd9267edb
Binary files /dev/null and b/docs/images/DeleteTransactionActivityDiagram.png differ
diff --git a/docs/images/DeleteTransactionClassDiagram.png b/docs/images/DeleteTransactionClassDiagram.png
new file mode 100644
index 00000000000..31d969569e7
Binary files /dev/null and b/docs/images/DeleteTransactionClassDiagram.png differ
diff --git a/docs/images/EditBookmarkTransactionClassDiagram.png b/docs/images/EditBookmarkTransactionClassDiagram.png
new file mode 100644
index 00000000000..05f6c90a007
Binary files /dev/null and b/docs/images/EditBookmarkTransactionClassDiagram.png differ
diff --git a/docs/images/EditTransactionActivityDiagram.png b/docs/images/EditTransactionActivityDiagram.png
new file mode 100644
index 00000000000..4188d667bb7
Binary files /dev/null and b/docs/images/EditTransactionActivityDiagram.png differ
diff --git a/docs/images/EditTransactionClassDiagram.png b/docs/images/EditTransactionClassDiagram.png
new file mode 100644
index 00000000000..25af3529db1
Binary files /dev/null and b/docs/images/EditTransactionClassDiagram.png differ
diff --git a/docs/images/EditTransactionSequenceDiagram.png b/docs/images/EditTransactionSequenceDiagram.png
new file mode 100644
index 00000000000..558b6b2923c
Binary files /dev/null and b/docs/images/EditTransactionSequenceDiagram.png differ
diff --git a/docs/images/EditTransactionSequenceDiagram2.png b/docs/images/EditTransactionSequenceDiagram2.png
new file mode 100644
index 00000000000..7c4dd1f787a
Binary files /dev/null and b/docs/images/EditTransactionSequenceDiagram2.png differ
diff --git a/docs/images/FindActivityDiagram.png b/docs/images/FindActivityDiagram.png
new file mode 100644
index 00000000000..176e280cd47
Binary files /dev/null and b/docs/images/FindActivityDiagram.png differ
diff --git a/docs/images/FindClassDiagram.png b/docs/images/FindClassDiagram.png
new file mode 100644
index 00000000000..646c91b6bce
Binary files /dev/null and b/docs/images/FindClassDiagram.png differ
diff --git a/docs/images/FindSequenceDiagram.png b/docs/images/FindSequenceDiagram.png
new file mode 100644
index 00000000000..904ebf92d3d
Binary files /dev/null and b/docs/images/FindSequenceDiagram.png differ
diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png
index b9e853cef12..a2fad445f1a 100644
Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ
diff --git a/docs/images/LogicStorageDIP.png b/docs/images/LogicStorageDIP.png
deleted file mode 100644
index 871157f5a9c..00000000000
Binary files a/docs/images/LogicStorageDIP.png and /dev/null differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index 280064118cf..81871530d65 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/ParsingAddBookmarkExpenseInputSequenceDiagram.png b/docs/images/ParsingAddBookmarkExpenseInputSequenceDiagram.png
new file mode 100644
index 00000000000..da7506c0a34
Binary files /dev/null and b/docs/images/ParsingAddBookmarkExpenseInputSequenceDiagram.png differ
diff --git a/docs/images/ParsingConvertBookmarkExpenseInputSequenceDiagram.png b/docs/images/ParsingConvertBookmarkExpenseInputSequenceDiagram.png
new file mode 100644
index 00000000000..990f0732f67
Binary files /dev/null and b/docs/images/ParsingConvertBookmarkExpenseInputSequenceDiagram.png differ
diff --git a/docs/images/SetExpenseLimitSequenceDiagram.png b/docs/images/SetExpenseLimitSequenceDiagram.png
new file mode 100644
index 00000000000..25a15745208
Binary files /dev/null and b/docs/images/SetExpenseLimitSequenceDiagram.png differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index d87c1216820..a5b53835a34 100644
Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ
diff --git a/docs/images/TimekeeperSequenceDiagram.png b/docs/images/TimekeeperSequenceDiagram.png
new file mode 100644
index 00000000000..f71bda0761b
Binary files /dev/null and b/docs/images/TimekeeperSequenceDiagram.png differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 5bd77847aa2..5856e676ef9 100644
Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ
diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png
index 7b4b3dbea45..15c10cc0665 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/UndoRedoState0.png b/docs/images/UndoRedoState0.png
deleted file mode 100644
index 8f7538cd884..00000000000
Binary files a/docs/images/UndoRedoState0.png and /dev/null differ
diff --git a/docs/images/UndoRedoState1.png b/docs/images/UndoRedoState1.png
deleted file mode 100644
index df9908d0948..00000000000
Binary files a/docs/images/UndoRedoState1.png and /dev/null differ
diff --git a/docs/images/UndoRedoState2.png b/docs/images/UndoRedoState2.png
deleted file mode 100644
index 36519c1015b..00000000000
Binary files a/docs/images/UndoRedoState2.png and /dev/null differ
diff --git a/docs/images/UndoRedoState3.png b/docs/images/UndoRedoState3.png
deleted file mode 100644
index 19959d01712..00000000000
Binary files a/docs/images/UndoRedoState3.png and /dev/null differ
diff --git a/docs/images/UndoRedoState4.png b/docs/images/UndoRedoState4.png
deleted file mode 100644
index 4c623e4f2c5..00000000000
Binary files a/docs/images/UndoRedoState4.png and /dev/null differ
diff --git a/docs/images/UndoRedoState5.png b/docs/images/UndoRedoState5.png
deleted file mode 100644
index 84ad2afa6bd..00000000000
Binary files a/docs/images/UndoRedoState5.png and /dev/null differ
diff --git a/docs/images/UndoSequenceDiagram.png b/docs/images/UndoSequenceDiagram.png
deleted file mode 100644
index 6addcd3a8d9..00000000000
Binary files a/docs/images/UndoSequenceDiagram.png and /dev/null differ
diff --git a/docs/images/add-remark/$Remark.png b/docs/images/add-remark/$Remark.png
deleted file mode 100644
index 959c634406d..00000000000
Binary files a/docs/images/add-remark/$Remark.png and /dev/null differ
diff --git a/docs/images/add-remark/CommandInterface.png b/docs/images/add-remark/CommandInterface.png
deleted file mode 100644
index b52e7811c52..00000000000
Binary files a/docs/images/add-remark/CommandInterface.png and /dev/null differ
diff --git a/docs/images/add-remark/ContextMenu.png b/docs/images/add-remark/ContextMenu.png
deleted file mode 100644
index 77536724e45..00000000000
Binary files a/docs/images/add-remark/ContextMenu.png and /dev/null differ
diff --git a/docs/images/add-remark/CreateTest.png b/docs/images/add-remark/CreateTest.png
deleted file mode 100644
index 6b7d6dcafec..00000000000
Binary files a/docs/images/add-remark/CreateTest.png and /dev/null differ
diff --git a/docs/images/add-remark/GradleRun.png b/docs/images/add-remark/GradleRun.png
deleted file mode 100644
index 281cc45f098..00000000000
Binary files a/docs/images/add-remark/GradleRun.png and /dev/null differ
diff --git a/docs/images/add-remark/ParserInterface.png b/docs/images/add-remark/ParserInterface.png
deleted file mode 100644
index 60c7892a534..00000000000
Binary files a/docs/images/add-remark/ParserInterface.png and /dev/null differ
diff --git a/docs/images/add-remark/RemarkBound.png b/docs/images/add-remark/RemarkBound.png
deleted file mode 100644
index d335382b286..00000000000
Binary files a/docs/images/add-remark/RemarkBound.png and /dev/null differ
diff --git a/docs/images/add-remark/RemarkComplete.png b/docs/images/add-remark/RemarkComplete.png
deleted file mode 100644
index 124ced2c752..00000000000
Binary files a/docs/images/add-remark/RemarkComplete.png and /dev/null differ
diff --git a/docs/images/add-remark/RemarkFailureOutput.png b/docs/images/add-remark/RemarkFailureOutput.png
deleted file mode 100644
index 351257ea332..00000000000
Binary files a/docs/images/add-remark/RemarkFailureOutput.png and /dev/null differ
diff --git a/docs/images/add-remark/RemarkHello.png b/docs/images/add-remark/RemarkHello.png
deleted file mode 100644
index aad48d02f8f..00000000000
Binary files a/docs/images/add-remark/RemarkHello.png and /dev/null differ
diff --git a/docs/images/add-remark/RemarkNotImplemented.png b/docs/images/add-remark/RemarkNotImplemented.png
deleted file mode 100644
index 1d187f39403..00000000000
Binary files a/docs/images/add-remark/RemarkNotImplemented.png and /dev/null differ
diff --git a/docs/images/findAlexDavidResult.png b/docs/images/findAlexDavidResult.png
deleted file mode 100644
index 235da1c273e..00000000000
Binary files a/docs/images/findAlexDavidResult.png and /dev/null differ
diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png
deleted file mode 100644
index b1f70470137..00000000000
Binary files a/docs/images/helpMessage.png and /dev/null differ
diff --git a/docs/images/ianyong.png b/docs/images/ianyong.png
new file mode 100644
index 00000000000..0bd8c922371
Binary files /dev/null and b/docs/images/ianyong.png differ
diff --git a/docs/images/johndoe.png b/docs/images/johndoe.png
deleted file mode 100644
index 1ce7ce16dc8..00000000000
Binary files a/docs/images/johndoe.png and /dev/null differ
diff --git a/docs/images/ppp/ianyong/NoExternalSite.png b/docs/images/ppp/ianyong/NoExternalSite.png
new file mode 100644
index 00000000000..fa0da18e15a
Binary files /dev/null and b/docs/images/ppp/ianyong/NoExternalSite.png differ
diff --git a/docs/images/remove/$address.png b/docs/images/remove/$address.png
deleted file mode 100644
index cf0434e0e83..00000000000
Binary files a/docs/images/remove/$address.png and /dev/null differ
diff --git a/docs/images/remove/SafeDeleteConflicts.png b/docs/images/remove/SafeDeleteConflicts.png
deleted file mode 100644
index 8f0abeffd4d..00000000000
Binary files a/docs/images/remove/SafeDeleteConflicts.png and /dev/null differ
diff --git a/docs/images/remove/UnsafeDelete.png b/docs/images/remove/UnsafeDelete.png
deleted file mode 100644
index 9e376d02a0c..00000000000
Binary files a/docs/images/remove/UnsafeDelete.png and /dev/null differ
diff --git a/docs/images/remove/UnsafeDeleteOnField.png b/docs/images/remove/UnsafeDeleteOnField.png
deleted file mode 100644
index 44d5bb0a442..00000000000
Binary files a/docs/images/remove/UnsafeDeleteOnField.png and /dev/null differ
diff --git a/docs/images/request_access.png b/docs/images/request_access.png
deleted file mode 100644
index 12e8a81bd28..00000000000
Binary files a/docs/images/request_access.png and /dev/null differ
diff --git a/docs/images/siddarth2824.png b/docs/images/siddarth2824.png
new file mode 100644
index 00000000000..dc87a27bbd7
Binary files /dev/null and b/docs/images/siddarth2824.png differ
diff --git a/docs/images/tracing/DebuggerStep1.png b/docs/images/tracing/DebuggerStep1.png
deleted file mode 100644
index 6d088ae63de..00000000000
Binary files a/docs/images/tracing/DebuggerStep1.png and /dev/null differ
diff --git a/docs/images/tracing/EditCommand.png b/docs/images/tracing/EditCommand.png
deleted file mode 100644
index ed34ad08b98..00000000000
Binary files a/docs/images/tracing/EditCommand.png and /dev/null differ
diff --git a/docs/images/tracing/Execute.png b/docs/images/tracing/Execute.png
deleted file mode 100644
index 66b7044e207..00000000000
Binary files a/docs/images/tracing/Execute.png and /dev/null differ
diff --git a/docs/images/tracing/FindUsages.png b/docs/images/tracing/FindUsages.png
deleted file mode 100644
index 16f94a53d09..00000000000
Binary files a/docs/images/tracing/FindUsages.png and /dev/null differ
diff --git a/docs/images/tracing/LeftGutter.png b/docs/images/tracing/LeftGutter.png
deleted file mode 100644
index 571acf99e7b..00000000000
Binary files a/docs/images/tracing/LeftGutter.png and /dev/null differ
diff --git a/docs/images/tracing/LogicSequenceDiagram.png b/docs/images/tracing/LogicSequenceDiagram.png
deleted file mode 100644
index c9b1f6cc232..00000000000
Binary files a/docs/images/tracing/LogicSequenceDiagram.png and /dev/null differ
diff --git a/docs/images/tracing/ShowExecutionPoint.png b/docs/images/tracing/ShowExecutionPoint.png
deleted file mode 100644
index ea72176fa64..00000000000
Binary files a/docs/images/tracing/ShowExecutionPoint.png and /dev/null differ
diff --git a/docs/images/tracing/StepInto.png b/docs/images/tracing/StepInto.png
deleted file mode 100644
index ddfa0e2aeb8..00000000000
Binary files a/docs/images/tracing/StepInto.png and /dev/null differ
diff --git a/docs/images/tracing/StepOver.png b/docs/images/tracing/StepOver.png
deleted file mode 100644
index ed5fb276e29..00000000000
Binary files a/docs/images/tracing/StepOver.png and /dev/null differ
diff --git a/docs/images/tracing/StructureToolWindow.png b/docs/images/tracing/StructureToolWindow.png
deleted file mode 100644
index c377c331d5f..00000000000
Binary files a/docs/images/tracing/StructureToolWindow.png and /dev/null differ
diff --git a/docs/images/tracing/Variables.png b/docs/images/tracing/Variables.png
deleted file mode 100644
index 02ea7b15520..00000000000
Binary files a/docs/images/tracing/Variables.png and /dev/null differ
diff --git a/docs/images/userguide/AddExpense.png b/docs/images/userguide/AddExpense.png
new file mode 100644
index 00000000000..230f1cf9276
Binary files /dev/null and b/docs/images/userguide/AddExpense.png differ
diff --git a/docs/images/userguide/AddIncome.png b/docs/images/userguide/AddIncome.png
new file mode 100644
index 00000000000..58061034923
Binary files /dev/null and b/docs/images/userguide/AddIncome.png differ
diff --git a/docs/images/userguide/AnalyticsTab.png b/docs/images/userguide/AnalyticsTab.png
new file mode 100644
index 00000000000..5296961805a
Binary files /dev/null and b/docs/images/userguide/AnalyticsTab.png differ
diff --git a/docs/images/userguide/EditExpense.png b/docs/images/userguide/EditExpense.png
new file mode 100644
index 00000000000..6ddfa5db5d9
Binary files /dev/null and b/docs/images/userguide/EditExpense.png differ
diff --git a/docs/images/userguide/EditIncome.png b/docs/images/userguide/EditIncome.png
new file mode 100644
index 00000000000..fed04fa7910
Binary files /dev/null and b/docs/images/userguide/EditIncome.png differ
diff --git a/docs/images/userguide/ExpensesTab.png b/docs/images/userguide/ExpensesTab.png
new file mode 100644
index 00000000000..11cd4546e82
Binary files /dev/null and b/docs/images/userguide/ExpensesTab.png differ
diff --git a/docs/images/userguide/IncomesTab.png b/docs/images/userguide/IncomesTab.png
new file mode 100644
index 00000000000..b53bf7d62ea
Binary files /dev/null and b/docs/images/userguide/IncomesTab.png differ
diff --git a/docs/images/userguide/OverviewTab.png b/docs/images/userguide/OverviewTab.png
new file mode 100644
index 00000000000..5856e676ef9
Binary files /dev/null and b/docs/images/userguide/OverviewTab.png differ
diff --git a/docs/images/userguide/SetExpenseLimit.png b/docs/images/userguide/SetExpenseLimit.png
new file mode 100644
index 00000000000..a777c788063
Binary files /dev/null and b/docs/images/userguide/SetExpenseLimit.png differ
diff --git a/docs/images/userguide/SetSavingsGoal.png b/docs/images/userguide/SetSavingsGoal.png
new file mode 100644
index 00000000000..b74ad6c57c7
Binary files /dev/null and b/docs/images/userguide/SetSavingsGoal.png differ
diff --git a/docs/images/userguide/UserGuide.png b/docs/images/userguide/UserGuide.png
new file mode 100644
index 00000000000..9abf18af28b
Binary files /dev/null and b/docs/images/userguide/UserGuide.png differ
diff --git a/docs/images/userguide/bookmark/AnnotatedAddBookmarkExpense.png b/docs/images/userguide/bookmark/AnnotatedAddBookmarkExpense.png
new file mode 100644
index 00000000000..3e31a51b254
Binary files /dev/null and b/docs/images/userguide/bookmark/AnnotatedAddBookmarkExpense.png differ
diff --git a/docs/images/userguide/bookmark/AnnotatedAddBookmarkIncome.png b/docs/images/userguide/bookmark/AnnotatedAddBookmarkIncome.png
new file mode 100644
index 00000000000..eb3a3d329be
Binary files /dev/null and b/docs/images/userguide/bookmark/AnnotatedAddBookmarkIncome.png differ
diff --git a/docs/images/userguide/bookmark/AnnotatedBookmarkExpenseOverview.png b/docs/images/userguide/bookmark/AnnotatedBookmarkExpenseOverview.png
new file mode 100644
index 00000000000..7b4632b0e84
Binary files /dev/null and b/docs/images/userguide/bookmark/AnnotatedBookmarkExpenseOverview.png differ
diff --git a/docs/images/userguide/bookmark/AnnotatedBookmarkIncomeOverview.png b/docs/images/userguide/bookmark/AnnotatedBookmarkIncomeOverview.png
new file mode 100644
index 00000000000..ef96ff92529
Binary files /dev/null and b/docs/images/userguide/bookmark/AnnotatedBookmarkIncomeOverview.png differ
diff --git a/docs/images/userguide/bookmark/AnnotatedConvertBookmarkExpense.png b/docs/images/userguide/bookmark/AnnotatedConvertBookmarkExpense.png
new file mode 100644
index 00000000000..560db020eae
Binary files /dev/null and b/docs/images/userguide/bookmark/AnnotatedConvertBookmarkExpense.png differ
diff --git a/docs/images/userguide/bookmark/AnnotatedConvertBookmarkIncome.png b/docs/images/userguide/bookmark/AnnotatedConvertBookmarkIncome.png
new file mode 100644
index 00000000000..fb14a719efc
Binary files /dev/null and b/docs/images/userguide/bookmark/AnnotatedConvertBookmarkIncome.png differ
diff --git a/docs/images/userguide/bookmark/AnnotatedEditBookmarkExpense.png b/docs/images/userguide/bookmark/AnnotatedEditBookmarkExpense.png
new file mode 100644
index 00000000000..9af46efd81f
Binary files /dev/null and b/docs/images/userguide/bookmark/AnnotatedEditBookmarkExpense.png differ
diff --git a/docs/images/userguide/bookmark/AnnotatedEditBookmarkIncome.png b/docs/images/userguide/bookmark/AnnotatedEditBookmarkIncome.png
new file mode 100644
index 00000000000..1366ee3b69b
Binary files /dev/null and b/docs/images/userguide/bookmark/AnnotatedEditBookmarkIncome.png differ
diff --git a/docs/images/wltan.png b/docs/images/wltan.png
new file mode 100644
index 00000000000..d8b78ed1e8c
Binary files /dev/null and b/docs/images/wltan.png differ
diff --git a/docs/images/yongping827.png b/docs/images/yongping827.png
new file mode 100644
index 00000000000..4e89e509c78
Binary files /dev/null and b/docs/images/yongping827.png differ
diff --git a/docs/images/zhaojj2209.png b/docs/images/zhaojj2209.png
new file mode 100644
index 00000000000..a7a34150faf
Binary files /dev/null and b/docs/images/zhaojj2209.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..563343ddb37 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,19 +1,26 @@
---
layout: page
-title: AddressBook Level-3
+title: Fine$$e
---
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
-[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3)
+[![CI Status](https://github.com/AY2021S1-CS2103T-W16-3/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2021S1-CS2103T-W16-3/tp/actions)
+[![codecov](https://codecov.io/gh/AY2021S1-CS2103T-W16-3/tp/branch/master/graph/badge.svg)](https://codecov.io/gh/AY2021S1-CS2103T-W16-3/tp)
![Ui](images/Ui.png)
-**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
-
-* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
-* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
+**Fine$$e is a desktop app for managing finances, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, Fine\$\$e can track and help you cultivate good financial habits faster than traditional GUI apps.
+* If you are interested in using Fine$$e, head over to the [_Quick Start_ section of the **User Guide**](https://ay2021s1-cs2103t-w16-3.github.io/tp/UserGuide.html#2-quick-start).
+* If you are interested in developing Fine$$e, the [**Developer Guide**](https://ay2021s1-cs2103t-w16-3.github.io/tp/DeveloperGuide.html) is a good place to start.
**Acknowledgements**
-* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5)
+* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org).
+* Libraries used:
+ * [Git Hooks Gradle Plugin](https://plugins.gradle.org/plugin/com.github.jakemarsden.git-hooks) *(licensed under the [MIT License](https://github.com/jakemarsden/git-hooks-gradle-plugin/blob/master/LICENSE))*
+ * [Gradle plugin for PIT Mutation Testing](https://plugins.gradle.org/plugin/info.solidsoft.pitest)
+ * [JavaFX](https://openjfx.io/)
+ * [Jackson](https://github.com/FasterXML/jackson)
+ * [Jekyll Spaceship](https://rubygems.org/gems/jekyll-spaceship) *(licensed under the [MIT License](https://github.com/jakemarsden/git-hooks-gradle-plugin/blob/master/LICENSE))*
+ * [JUnit5](https://github.com/junit-team/junit5)
+ * [PlantUML](https://plantuml.com/)
diff --git a/docs/minutes/20200829.md b/docs/minutes/20200829.md
new file mode 100644
index 00000000000..bd56dc4dac8
--- /dev/null
+++ b/docs/minutes/20200829.md
@@ -0,0 +1,30 @@
+---
+layout: minutes
+date: 2020-08-29
+---
+
+### Product Name
+
+FINe$$e
+
+### Target user
+
+Young adults aged 18-24 without a steady income who wish to better manage their finances.
+
+### Value Proposition
+
+Allow young adults to manage their expenditure and also have better control over their finances. Furthermore, it will promote better financial habits such as saving.
+
+### Finance tracker
+
+Common features:
+1. Expenditure & Income
+ - Recurring bills/payments: (e.g.: Phone bills, netflix and spotify subscriptions)/ Payment due (owe to friends, other form of payments)
+2. Categories
+ - User can add custom ones
+3. Analytics
+ - Summary (by date, by categories)
+ - Reports based on user’s input
+ - Sankey Diagram
+4. Budgeting (Daily, weekly, monthly, by categories?)
+ - Savings goals
diff --git a/docs/minutes/20200905.md b/docs/minutes/20200905.md
new file mode 100644
index 00000000000..77c652594df
--- /dev/null
+++ b/docs/minutes/20200905.md
@@ -0,0 +1,59 @@
+---
+layout: minutes
+date: 2020-09-05
+---
+
+{:theme-1: style="background-color: #F4CCCC;"}
+{:theme-2: style="background-color: #FCE5CD;"}
+{:theme-3: style="background-color: #FFF2CC;"}
+{:theme-4: style="background-color: #D9EAD3;"}
+{:theme-5: style="background-color: #CFE2F3;"}
+{:theme-6: style="background-color: #D9D2E9;"}
+{:theme-7: style="background-color: #EAD1DC;"}
+
+### User Stories
+
+|No.|As a/an {user type/role}|I can {function}|so that {benefit}|
+|-|-|-|-|
+|Theme: As a user, I can track my everyday expenses {: theme-1}||||
+|1.1|User|Set a daily spending limit to a specified amount|I can track how much I have left to spend for the day|
+|1.2|User|Set a weekly spending limit to a specified amount|I can track how much I have left to spend for the week|
+|1.3|User|Set a monthly spending limit to a specified amount|I can track how much I have left to spend for the month|
+|1.4|User|See how much I have remaining before the limit is reached|I can keep my expenses within that limit|
+|1.5|User|Enter my expenditure after each payment|I can keep track of how much I have spent|
+|1.6|User|Enter a description for each payment|I can keep track of what I spend my money on|
+|1.7|User|Enter a category of each expenditure|I can keep track of what I spend my money on across categories|
+|1.8|Forgetful user|Get reminders of upcoming payments|I can make payments on time|
+|Theme: As a user, I can track my income {: theme-2}||||
+|2.1|User|Enter my regular income|I can track my income|
+|2.2|User|Enter my ad-hoc income|I can track my income|
+|2.3|User|Enter a description for my income|I can keep track of where my income was from|
+|Theme: As a user, I can track my savings {: theme-3}||||
+|3.1|User|Compare my income to my expenditure|I can determine how much I have saved|
+|3.2|User|Set daily saving goals|I can cultivate good saving habits|
+|3.3|User|Set weekly saving goals|I can cultivate good saving habits|
+|3.4|User|Set monthly saving goals|I can cultivate good saving habits|
+|3.5|User|Set specific saving goals|I can save up enough for a specific item or service|
+|Theme: As a user, I can analyze my saving/spending trends {: theme-4}||||
+|4.1|User|See my saving trends in the form of visual data representations|I can better plan my future expenses|
+|4.2|User|See my spending trends in the form of visual data representations|I can gauge my expenses and cut down if needed|
+|Theme: As a user, I can input commands quickly using CLI inputs {: theme-5}||||
+|5.1|User|Autocomplete commands based on what has been typed so far|I can type commands faster|
+|5.2|User|Define shortcuts for tasks|I can save time on frequently performed tasks|
+|Theme: As a user, I can control when and how my data is stored {: theme-6}||||
+|6.1|User|Export and import my data|I can transfer my data from one device to another|
+|6.2|User|Back up my data|I can recover my data in the event it gets lost|
+|Theme: As a user, I would like a sense of pride and accomplishment when working towards my saving goals {: theme-7}||||
+|7.1|User|Complete gamified savings challenges|I can feel rewarded for reaching my savings goals and encouraged to continue reaching more saving goals|
+|7.2|User|See my long-term accomplishments in the form of animated objects|I can feel a sense of satisfaction|
+|7.3|User|Be rewarded for consistently using the application|I am motivated to consistently track my expenses|
+
+### Rejected User Stories
+
+|No.|As a/an {user type/role}|I can {function}|so that {benefit}|Reason for rejection|
+|-|-|-|-|-|
+|1|User|Receive a forfeit for going over expenditure limit|I can feel punished for going over my expenditure limit and discouraged from going over the limit in the future|Encourages the user to not report/ under-report expenditures, which is undesired behaviour|
+|2|User|Select valid commands from a drop-down thingy|I can input commands faster|Focus more on CLI; does not relate well to the user being a fast typist and preferring typing over other means of input|
+|3|User|Interact with the system via a chatbot interface|The application feels more personal|Too much work; does not really contribute to expense tracking|
+|4|User|Synchronisation of application data to the cloud|My data will always be backed up in the cloud|Remote server is not allowed due to project constraints|
+|5|User|Link to bank API|My expenses and income will automatically be tracked by the application|Remote server is not allowed due to project constraints. In addition, banks do not expose such APIs for public consumption due to security concerns|
diff --git a/docs/minutes/20200912.md b/docs/minutes/20200912.md
new file mode 100644
index 00000000000..e062a835dd2
--- /dev/null
+++ b/docs/minutes/20200912.md
@@ -0,0 +1,41 @@
+---
+layout: minutes
+date: 2020-09-12
+---
+
+### Features
+
+- Finance Tracking
+ - Expenses
+ - Adding expense: `add-expense` / `adde`
+ - List expense: `ls-expense` / `lse`
+ - Delete expense: `rm-expense` / `rme`
+ - Income
+ - Adding income: `add-income` / `addi`
+ - List income: `ls-income` / `lsi`
+ - Delete income: `rm-income` / `rmi`
+ - Savings
+ - Check savings: `savings`
+- Exiting the program: `exit`
+- Viewing help: `help`
+- Saving the data
+
+### Actions
+
+- CI job (Wei Liang)
+- Gradle stuff (Ian)
+- Git projects
+ - Create issues (all, based on UG assignment)
+ - Create projects (Yong Ping)
+ - Fine$$e (General)
+ - Finance Tracking (Expenses)
+ - Finance Tracking (Income)
+ - Finance Tracking (Savings)
+ - Assign issues to projects (issue creator)
+ - Assign members to issues (discuss again)
+- Labels (Jingjing)
+ - PR status
+ - Type
+ - Priority
+- Upload the UG (Siddarth)
+
diff --git a/docs/minutes/20200919.md b/docs/minutes/20200919.md
new file mode 100644
index 00000000000..25009178d76
--- /dev/null
+++ b/docs/minutes/20200919.md
@@ -0,0 +1,40 @@
+---
+layout: minutes
+date: 2020-09-19
+---
+
+### Roles
+
+- Ian Yong
+ - Team Co-Lead
+ - Testing
+- Wei Liang
+ - Team Co-Lead
+ - Git/CI Expert, Integration
+- Siddarth
+ - Scheduling and Tracking
+ - User Interface In-Charge
+- Jingjing
+ - Documentation
+ - Model/Logic In-Charge
+- Yong Ping
+ - Code Quality
+ - Storage In-Charge
+
+### v1.1 Tasks
+
+- Project Website
+ - About Us (by Monday night)
+ - Everyone to create PR for themselves
+ - Readme (by Monday night)
+ - UI Mockup basic (Siddarth)
+- User Guide (Done)
+- Developer Guide (Jingjing, Yong Ping)
+ - User stories (Jingjing)
+ - Glossary (Yong Ping)
+ - Non-functional requirements (Yong Ping)
+ - Use cases (Jingjing)
+- Transfer minutes to GitHub Pages (Ian)
+- PIT testing (Wei Liang)
+- Git hooks (Ian)
+- Planning for v1.2 (once all of the above are completed)
diff --git a/docs/minutes/20200925.md b/docs/minutes/20200925.md
new file mode 100644
index 00000000000..ba2d9a520bc
--- /dev/null
+++ b/docs/minutes/20200925.md
@@ -0,0 +1,43 @@
+---
+layout: minutes
+date: 2020-09-25
+---
+
+_\*Foreword: Meetings have been shifted from Saturdays to Fridays_
+
+### Tasks for v1.2 (Due: 11th October)
+
+- Renaming (Yong Ping)
+ - Person → Transaction
+ - Name → Name
+ - Phone → Amount
+ - Tags → Category
+ - Email → Date
+ - Address → (deleted)
+- Slowly fix tests
+ - Test data for Name/Amount/Category
+- UI
+ - Make it look like the mock-up (Siddarth)
+- Models
+ - Set up expenses, income, etc once transaction is up (Jingjing)
+ - Modify AB3 to accommodate two lists (expenses and income)
+- Commands
+ - Expenses (Ian)
+ - Adding expense: `add-expense` / `adde`
+ - List expense: `ls-expense` / `lse`
+ - Delete expense: `rm-expense` / `rme`
+ - Income (Wei Liang)
+ - Adding income: `add-income` / `addi`
+ - List income: `ls-income` / `lsi`
+ - Delete income: `rm-income` / `rmi`
+- To be delegated at a later date:
+ - Savings
+ - Check savings: `savings`
+ - Exiting the program: `exit`
+ - Viewing help: `help`
+ - Delete unused commands
+- Integration
+ - OTOT pull from other teammates' branches to test integration
+- Documentation
+ - UG at the end of v1.2
+ - DG at the end of v1.2
diff --git a/docs/minutes/20201002.md b/docs/minutes/20201002.md
new file mode 100644
index 00000000000..ae90f02e414
--- /dev/null
+++ b/docs/minutes/20201002.md
@@ -0,0 +1,76 @@
+---
+layout: minutes
+date: 2020-10-02
+---
+
+### Standards for Issues and PRs
+
+Issues from user stories:
+- Refer to user story issue in body
+- Create at the start of each iteration, or at the end of each team meeting
+
+Issues from PRs or follow-up from other issues:
+- If you see a possible enhancement or problem while fixing your issue,
+ - If it is dependent on your issue in some way, document it under your issue
+ - If it is not dependent on your issue, create a new issue. Refer to the original issue if you want.
+- If you want to add extensions to the issue in the same PR:
+ - Create a new issue
+ - Rationale: So that others are aware you are working on this extension and will avoid unnecessary double work
+ - Add a new commit for the extension
+ - Rationale: So it is captured in commit message when squashed
+ - Refer to it in the PR
+- Definition of extension:
+ - An enhancement that is not required to meet the specifications of the issue, that modifies the behavior of the program
+ - Rationale:
+ - Behavior changes must be closely monitored to prevent regressions from slipping past reviewers
+ - Try to keep independent behavior changes apart from each other
+ - Definition of behavior:
+ - CLI input or outputs change
+ - GUI input or outputs change
+
+Changes not requiring issues:
+- Can be put under another PR or be a standalone PR without reference to issues
+ - If put under another PR, put in a separate commit (so that it is captured in commit message when squashed)
+- Examples:
+ - Typos
+ - Formatting / style changes
+ - Anything under 10 lines not affecting behavior
+
+### Tasks for v1.2 (11th October)
+*updated*
+- ~~*Renaming (Yong Ping)*~~
+ - ~~*Person → Transaction*~~
+ - ~~*Name → Name*~~
+ - ~~*Phone → Amount*~~
+ - ~~*Tags → Category*~~
+ - ~~*Email → Date*~~
+ - ~~*Delete Address (Ian)*~~
+- Slowly fix tests
+ - Test data for Description/Amount/Category
+- UI
+ - Make it look like the mock-up (Siddarth)
+- ~~*Models*~~
+ - ~~*Set up expenses, income, etc, once transaction is up (Jingjing)*~~
+- Commands
+ - Expenses (Ian)
+ - Adding expense: `add-expense` / `adde`
+ - List expense: `ls-expense` / `lse`
+ - Delete expense: `rm-expense` / `rme`
+ - Income (Wei Liang)
+ - Adding income: `add-income` / `addi`
+ - List income: `ls-income` / `lsi`
+ - Delete income: `rm-income` / `rmi`
+- To be delegated at a later date:
+ - Savings
+ - Check savings: `savings`
+ - Exiting the program: `exit`
+ - Viewing help: `help`
+ - Delete unused commands
+ - *Modify AddressBook to accommodate two lists (expenses and income)*
+- Integration
+ - OTOT pull from other teammates' branches to test integration
+- Documentation
+ - UG at the end of v1.2
+ - DG at the end of v1.2
+
+**Next meeting on Tuesday (6 Oct)**
diff --git a/docs/minutes/20201006.md b/docs/minutes/20201006.md
new file mode 100644
index 00000000000..ef12f71f162
--- /dev/null
+++ b/docs/minutes/20201006.md
@@ -0,0 +1,45 @@
+---
+layout: minutes
+date: 2020-10-06
+---
+
+### Usage of Labels
+
+- Bug :bug: : Unintended behaviour that needs to be addressed
+- Enhancement :+1: : Any improvements to the product
+- Documentation :roll_of_paper: : Improvements or additions to documentation
+- Task :clipboard: : None of the above labels
+
+### Progress Update
+
+- Siddarth
+ - Working on UI styling currently
+ - To put up a PR with the base UI structure as soon as possible so that integration can begin
+- Wei Liang
+ - Fix Amount and Date validation
+ - To put PR by 6 Oct, target to merge by 7 Oct
+- Yong Ping
+ - Added expense and income lists
+ - Currently modifying `FinanceTracker` to include expense and income lists
+ - Require “Fix Amount and Date validation” from Wei Liang
+- Jingjing
+ - Renamed address book
+ - Removed duplicate checks
+- Ian
+ - Done with tasks
+ - Waiting to start work on new features
+
+Pull Request Review SOP
+
+- Clone PR to local machine
+- Run system tests
+- Perform exploratory testing
+- If there are code changes, run **pitest** to make sure the code is properly covered by tests
+ - Compare report with master branch
+- Check diff on github
+ - Make sure there are no unintended changes
+ - Make sure that intended changes are there
+ - Check for typos
+ - Check for AB3 residue (fix-as-you-go policy)
+ - AddressBook names
+ - Javadocs (missing full stop, wrong documentation, grammar problems)
diff --git a/docs/minutes/20201010.md b/docs/minutes/20201010.md
new file mode 100644
index 00000000000..a77e43cf8e1
--- /dev/null
+++ b/docs/minutes/20201010.md
@@ -0,0 +1,37 @@
+---
+layout: minutes
+date: 2020-10-10
+---
+
+_\*Foreword: In lieu of CS2100 midterm assessment, the meeting was postponed to 10 October 2020._
+
+### Updated Architecture
+
+Issue: Need to support UI-dependent behaviour for commands.
+
+Solution: Pass the UI state to the command parser.
+
+### Actionables
+
+Wei Liang:
+- Create {Add, Delete} × {Expense, Income} Command
+ - Generic `AddCommand` that works based on the current `UiState`
+
+Ian:
+- Add UiState (Partially done)
+- Create {Edit, List} × {Expense, Income} Command
+ - Generic `ListCommand` that works based on the current `UiState`
+
+Yong Ping:
+- Edit `ModelManager.getFilteredTransactionList`
+- Remove `TransactionList`
+
+Jingjing:
+- Make `Transaction` abstract
+- Generic `FindCommand` that works based on the current `UiState`
+
+Siddarth:
+- Adding left side panel in the main window
+- Change menu tabs to tab panel
+- Center categories
+- Show expense list and income lists in the respective panels (hook logic to UI)
diff --git a/docs/minutes/20201016.md b/docs/minutes/20201016.md
new file mode 100644
index 00000000000..c0441f10c72
--- /dev/null
+++ b/docs/minutes/20201016.md
@@ -0,0 +1,51 @@
+---
+layout: minutes
+date: 2020-10-16
+---
+
+### v1.2 Postmortem
+*Fine$$e is coming into shape - Ian Yong, 2020*
+
+Our current workflow is working well and everyone is aware of our SOPs. However, we will be making minor workflow adjustments to make the process more convenient.
+
+#### Workflow changes
+ - Allowed to work on upstream branches instead of forks.
+ - Individuals can stick to forks if they want.
+ - Investigate feasibility of UI testing. If not viable, then remove it from the testing suite.
+
+### Actionables
+ - Single List Implementation
+ - `ModelManager`: 3 filteredLists for Transactions, Incomes and Expenses respectively, `FinanceTracker`: 1 transactionList **(Yong Ping)**
+ - Show the differentiation between Incomes, Expenses and Transactions in the Overview Tab **(Siddarth)**
+ - Storage **(Yong Ping)**
+ - 2 lists (expenses and incomes) in json data file
+ - Add frequent expenses and incomes
+ - Analytics **(Ian and Siddarth)**
+ - Check if we can get a POC for using data visualisation tools with Data2Viz and then request if the library can be used **(Siddarth)**
+ - Metrics to be displayed
+ - Monthly Expenses
+ - Monthly Incomes
+ - Monthly Savings
+ - Category expenditure within the month (To show which category the user has spent on the most for the month)
+ - Set monthly budget (budget == expense limit) **(Jingjing)**
+ - Budget class
+ - Monthly saving goals **(Jingjing)**
+ - Frequent monthly expenses and incomes **(Siddarth)**
+ - Add a class to store frequent expenses/incomes (such frequent expenses/incomes will be in a separate list)
+ - Add commands to add, edit, delete frequent expenses/incomes
+ - Add a command to convert frequent expenses/incomes to actual expense/incomes
+ - Check if json data reading validation is present **(Jingjing)**
+ - Dates, amounts, etc must all be in the correct format **(Throw error if someone messes with the data file)**
+ - Standardise transaction amount format **(Wei Liang)**
+ - Change the type of data in `Transaction` from `String` to `Currency`
+ - (Stretch Goal) Command: '$' prefix change to depend on locale
+ - Convert everything to $XX.XX format to read in input, then convert to numbers when calculating to analytics
+ - Standardise date format and add validation **(Wei Liang)**
+ - Prevent user from adding transactions which has a date that is later than the current date of entry
+ - Change the type of data in `Date` from `String` to `LocalDate`
+ - In Analytics, make use of `YearMonth` instead of `LocalDate`
+ - Bring up test coverage **(Ian)**
+ - Add suppression/exclusion for areas that are not feasible to cover
+ - Aim to reach 100% mutation coverage (after suppressions), then set pitest to fail build when coverage drops below 100% - prevent regression in test coverage
+ - Search transactions by field **(Jingjing)**
+ - Edit find commands to be similar to edit commands e.g `find INDEX KEYWORD`
diff --git a/docs/minutes/20201023.md b/docs/minutes/20201023.md
new file mode 100644
index 00000000000..4e49ba04fd0
--- /dev/null
+++ b/docs/minutes/20201023.md
@@ -0,0 +1,29 @@
+---
+layout: minutes
+date: 2020-10-23
+---
+
+### v1.3 Progress update
+- Analytics
+ - UI
+ - Yong Ping and Ian taking over
+ - Get library to work and seek permission for library (Yong Ping)
+ - Refactor how tabs are handled in UI for greater extensibility (Ian)
+ - Resizable window with minimum window size (Ian)
+ - Set up analytics panel
+ - Hook up visualisations to data
+ - Logic/Model (Jingjing)
+ - Monthly expense limit, monthly savings goal: by Saturday morning
+ - Calculation of remaining budget and savings: by Sunday
+ - Today’s date? Check when on analytics tab
+- Issue [#128](https://github.com/AY2021S1-CS2103T-W16-3/tp/issues/128): Expense and income lists no longer update in the UI (Wei Liang)
+ - Pending UI refactor
+- Refactor commands to be consistent
+ - Fix tab exception before parse exception (Wei Liang)
+- Refactor Frequent Expense → Bookmarked Expense (Wei Liang)
+- ~~Frequent~~ Bookmarked Income (Siddarth)
+ - Add Delete command for bookmarked incomes
+ - Add Edit command for bookmarked incomes
+ - Add Convert command for bookmarked incomes
+ - Filter bookmarked incomes by keywords
+ - Filter bookmarked expenses by keywords
diff --git a/docs/minutes/20201031.md b/docs/minutes/20201031.md
new file mode 100644
index 00000000000..6a03b99cd9b
--- /dev/null
+++ b/docs/minutes/20201031.md
@@ -0,0 +1,22 @@
+---
+layout: minutes
+date: 2020-10-31
+---
+
+*Foreword: In lieu of the late release of mock practical exam bug reports, the meeting was postponed to 31 October 2020.*
+
+Happy halloween! :jack_o_lantern: :ghost: :jack_o_lantern: :ghost: :jack_o_lantern:
+
+Mock Practical Exam
+- 16 total issues
+- Nothing too serious
+- Got a time traveller on our hands [#246](https://github.com/AY2021S1-CS2103T-W16-3/tp/issues/246)
+
+To-do for next week:
+- Finish fixing bugs in PE
+- Do PPP
+- Do DG (focus on the features that we have implemented ourselves)
+- If time allows: bring up code coverage
+
+Feature freeze:
+- [#221](https://github.com/AY2021S1-CS2103T-W16-3/tp/issues/221) and [#262](https://github.com/AY2021S1-CS2103T-W16-3/tp/issues/262) should be the final enhancement issues
diff --git a/docs/team/ianyong.md b/docs/team/ianyong.md
new file mode 100644
index 00000000000..7a0ac59d348
--- /dev/null
+++ b/docs/team/ianyong.md
@@ -0,0 +1,59 @@
+---
+layout: page
+title: Ian Yong's Project Portfolio Page
+---
+
+## Project: Fine$$e
+
+### Overview
+
+Fine$$e is a desktop finance tracker that allows tertiary students to better manage their finances, cultivating good financial habits such as saving. It is optimised for CLI users so that expenses and income can be tracked efficiently by typing in commands. It is written in Java, and has a GUI created with JavaFX.
+
+### Summary of Contributions
+
+* **New feature**: Embedded user guide within the application.
+ * What it does: Allows the user to view the [user guide](UserGuide.html) from within the application.
+ * Justification: The barriers to entry of using Fine\$\$e are high for new users as they are required to learn the various commands available.
+ By embedding the user guide within the application, it allows users to quickly look up Fine$$e's features and their corresponding commands.
+ This leads to a much better user experience.
+ * Highlights: JavaFX's `WebView` component was used to display the user guide.
+ Unfortunately, it does not adhere too closely to modern browser standards, resulting in certain pages not being rendered correctly.
+ In order to ensure that the user experience is smooth, the decision to only allow users to access pages in the domain `https://ay2021s1-cs2103t-w16-3.github.io/tp/` was made.
+ Should the user attempt to navigate to an external site, the following page is shown instead:
+ ![No external site page](../images/ppp/ianyong/NoExternalSite.png)
+
+* **New feature**: Added command history.
+ * What it does: Allows the user to cycle through their 50 most recent inputs via the ↑ and ↓ arrow keys.
+ * Justification: Almost all modern command line interfaces support the scrolling through of previously typed commands.
+ The addition of this feature allows users who are experienced with command line interfaces to be more efficent when using Fine$$e.
+ * Highlights: This feature necessitated the implementation of a stack with a maximum capacity so as not to run out of memory.
+ Once the stack is full, the bottom-most element of the stack is evicted.
+ The underlying data structure that backs the stack is a doubly linked list.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=&sort=groupTitle&sortWithin=title&since=2020-08-14&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&tabAuthor=ianyong&tabRepo=AY2021S1-CS2103T-W16-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other)
+
+* **Project management**:
+ * Updated GitHub Pages to display meeting minutes and Gantt chart.
+
+* **Enhancements to existing features**:
+ * Refactored the user interface to be more extensible. (Pull request [#161](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/161))
+ * Updated the user interface to be resizable and scalable. (Pull requests [#174](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/174), [#226](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/226))
+
+* **Documentation**:
+ * User Guide (Pull requests [#179](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/179), [#235](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/235)):
+ * Updated user guide to give an overview of the various tabs in Fine$$e.
+ * Added section on command history.
+ * Developer Guide (Pull requests [#141](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/141), [#318](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/318)):
+ * Added implementation details for tab switching.
+ * Added implementation details for command history.
+ * Added implementation details for data integrity safeguards.
+ * Updated design details for UI component.
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): [#39](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/39), [#75](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/75), [#123](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/123), [#232](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/232), [#264](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/264), [#265](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/265), [#285](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/285)
+ * Helped classmates in forum discussions. (Examples: [1](https://github.com/nus-cs2103-AY2021S1/forum/issues/14), [2](https://github.com/nus-cs2103-AY2021S1/forum/issues/117), [3](https://github.com/nus-cs2103-AY2021S1/forum/issues/191), [4](https://github.com/nus-cs2103-AY2021S1/forum/issues/200), [5](https://github.com/nus-cs2103-AY2021S1/forum/issues/249), [6](https://github.com/nus-cs2103-AY2021S1/forum/issues/262), [7](https://github.com/nus-cs2103-AY2021S1/forum/issues/269), [8](https://github.com/nus-cs2103-AY2021S1/forum/issues/276), [9](https://github.com/nus-cs2103-AY2021S1/forum/issues/386))
+ * Solution for removing bullet points from the table of contents of user guide was [adopted by several other classmates](https://github.com/nus-cs2103-AY2021S1/forum/issues/361).
+
+* **Tools**:
+ * Added Git pre-push hook to enforce code quality. (Pull request [#7](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/7))
+ * Updated GitHub Pages build process to use `jekyll-spaceship` for greater control over site formatting. (Pull request [#18](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/18))
diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md
deleted file mode 100644
index 1f1e9e6f6db..00000000000
--- a/docs/team/johndoe.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-layout: page
-title: John Doe's Project Portfolio Page
----
-
-## Project: AddressBook Level 3
-
-AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC.
-
-Given below are my contributions to the project.
-
-* **New Feature**: Added the ability to undo/redo previous commands.
- * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command.
- * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them.
- * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands.
- * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}*
-
-* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys.
-
-* **Code contributed**: [RepoSense link]()
-
-* **Project management**:
- * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub
-
-* **Enhancements to existing features**:
- * Updated the GUI color scheme (Pull requests [\#33](), [\#34]())
- * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]())
-
-* **Documentation**:
- * User Guide:
- * Added documentation for the features `delete` and `find` [\#72]()
- * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]()
- * Developer Guide:
- * Added implementation details of the `delete` feature.
-
-* **Community**:
- * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]()
- * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]())
- * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]())
- * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]())
-
-* **Tools**:
- * Integrated a third party library (Natty) to the project ([\#42]())
- * Integrated a new Github plugin (CircleCI) to the team repo
-
-* _{you can add/remove categories in the list above}_
diff --git a/docs/team/siddarth2824.md b/docs/team/siddarth2824.md
new file mode 100644
index 00000000000..a3231df75d8
--- /dev/null
+++ b/docs/team/siddarth2824.md
@@ -0,0 +1,51 @@
+---
+layout: page
+title: Siddarth Nandanahosur Suresh's Project Portfolio Page
+---
+
+## Project: Fine$$e
+
+### Overview
+
+Fine$$e is a desktop finance tracker that allows tertiary students to better manage their finances, cultivating good financial habits such as saving.
+It is optimised for CLI users so that expenses and income can be tracked efficiently by typing in commands.
+It is written in Java, and has a GUI created with JavaFX.
+
+### Summary of Contributions
+
+* **New Feature**: Developed the bookmark expense and income feature of Fine$$e.
+ * What it does: Allows university students to store and update, incomes and expenses that they make frequently.
+ It also allows users to convert bookmark incomes and expenses into incomes and expenses and store it in Fine$$e.
+ * Justification: This feature significantly improves the product’s functionality as it allows university students to conveniently add frequently occurring incomes and expenses without the hassle of keying in all of the information every time they want to add in a new income or expense to Fine$$e.
+ They can easily keep track of their bookmarked incomes and expenses and update them accordingly as well.
+ * Highlights: This enhancement features allows to store frequently expenses and incomes
+
+* **New Feature**: Assisted in the development of the UI/UX (User Interface / User Experience) of Fine$$e.
+ * What it does: Enhances the view of Fine$$e to be more pleasant and intuitive for the user to use.
+ * Justification: A product with a well-developed UI/UX is key to getting a user interested in using the application for a sustained period of time.
+ Fine$$e aims to create an attractive display and seamless experience for all our users.
+ * This enhancement saw a makeover of the original AddressBook3 interface.
+ Colors of each component in the application is carefully chosen to create a good blended theme.
+ The headers at the top will light up allowing for easy identification of which tab the user is on.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=siddarth2824&sort=groupTitle&sortWithin=title&since=2020-08-14&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+* **Project management**:
+ * Managed release `v1.2` (1 release) on GitHub.
+
+* **Enhancements to existing features**:
+ * Updated the GUI color scheme. (Pull requests [#75](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/75))
+ * Updated the GUI basic layout from AddressBook3. (Pull requests [#75](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/75), [#101](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/101))
+ * Updated the layout of the User Guide: (Pull requests [#169](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/169))
+
+* **Documentation**:
+ * User Guide:
+ * Updated User Guide to include Bookmark Expense and Income feature.
+ * Updated User Guide with the Introduction of Fine$$e.
+ * Developer Guide:
+ * Updated Developer Guide to include Bookmark Transactions feature.
+ * Updated the use cases to include use cases for Bookmark Transactions feature.
+
+* **Community**:
+ * PRs reviewed (with non-trivial comments): [#150](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/150#discussion_r510884713), [#197](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/197#discussion_r512544298)
+ * Reported bugs and suggestions for other teams in the class. (examples: Practical Exam Dry Run)
diff --git a/docs/team/wltan.md b/docs/team/wltan.md
new file mode 100644
index 00000000000..2d6cbe5dac7
--- /dev/null
+++ b/docs/team/wltan.md
@@ -0,0 +1,69 @@
+---
+layout: page
+title: Tan Wei Liang's Project Portfolio Page
+---
+
+## Project: Fine$$e
+
+### Overview
+
+Fine$$e is a desktop finance tracker that allows tertiary students to better manage their finances, cultivating good financial habits such as saving. It is optimised for CLI users so that expenses and income can be tracked efficiently by typing in commands. It is written in Java, and has a GUI created with JavaFX.
+
+### Summary of Contributions
+
+* **New feature**: Overhauled the validation rules for data models.
+ * What it does: Transforms the data model from the base Address Book project to one that is more suitable for the needs of Fine\$\$e.
+ * Justification: Previously, the project contained data models such as `Name` and `Email`.
+ In the initial stages of the project, to minimize the amount of development work needed we decided to map some of the existing models over to Fine\$\$e.
+ However, the existing models had some existing validation rules which do not make sense in Fine\$\$e.
+ * Highlights: To facilitate further operations on the data models, some of the underlying types have been changed from `String` to other Java data types.
+ However, a layer of abstraction is maintained such that other components do not need to depend on the underlying data types.
+ This includes converting the internal data from `public` to `private`, and using accessors to retrieve the data.
+
+* **New feature**: Drafted the initial design for UI-dependent commands.
+ * What it does: Allows the user to use commands with different behavior based on the current state of the user interface (in particular, which tab is currently active).
+ * Justification: This streamlines the user experience, as they only need to remember a small set of intuitive commands.
+ * Highlights: The existing commands were reused as much as possible, while complying with the standard OOP principles.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=wltan&sort=groupTitle&sortWithin=title&since=2020-08-14&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+* **Project management**:
+ * Set up the team [GitHub organization](https://github.com/AY2021S1-CS2103T-W16-3) and forked the tP repository.
+ * Checked for code quality infractions in PRs.
+ * Managed release `v1.3.1` and `v1.4` (2 releases) on GitHub.
+
+* **Enhancements to existing features**:
+ * Fixed obscure bugs. (Pull requests
+ [#168](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/168),
+ [#219](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/219),
+ [#225](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/225),
+ [#260](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/260))
+ * Restricted users from using certain commands for Bookmark Transactions. (Pull request [#196](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/196))
+ * Maintained code quality. (Pull requests [#284](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/284), [#304](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/304))
+
+* **Documentation**:
+ * User Guide:
+ * Updated user guide for Add Expense, Add Income, Edit Expense, Edit Income
+ commands for v1.3. (Pull request [#178](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/178))
+ * Fixed some user guide bugs between v1.3 and v1.3.1. (Pull request [#206](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/206))
+ * Developer Guide:
+ * Elaborated on the `Model` component documentation, and added Pitest to the DevOps guide. (Pull request [#140](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/140))
+ * Documented the implementation for add, edit, delete transaction features. (Pull request [#314](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/314))
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments):
+ [#123](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/123),
+ [#150](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/150),
+ [#166](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/166),
+ [#207](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/207),
+ [#257](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/257),
+ [#258](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/258).
+ Check [here](https://github.com/AY2021S1-CS2103T-W16-3/tp/pulls?q=is%3Apr+reviewed-by%3Awltan) for a full up-to-date listing.
+ * Contributions to Forum: [Forum link](https://github.com/nus-cs2103-AY2021S1/forum/issues?q=is%3Aissue+commenter%3Awltan)
+
+* **Tools**:
+ * Updated Gradle from `5.2.1` to `6.6.1`. (Pull request [#3](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/3))
+ * Set up [PIT mutation testing](http://pitest.org/) as a test coverage tool, as an alternative to JaCoCo and CodeCov.
+ (Pull request [#6](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/6))
+ * Automated PIT mutation testing as a CI job that generates artifacts to speed up the PR review process. (Pull request [#296](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/296))
+
diff --git a/docs/team/yongping827.md b/docs/team/yongping827.md
new file mode 100644
index 00000000000..10594d92bf7
--- /dev/null
+++ b/docs/team/yongping827.md
@@ -0,0 +1,40 @@
+---
+layout: page
+title: Yong Ping's Project Portfolio Page
+---
+
+## Project: Fine$$e
+
+### Overview
+
+Fine$$e is a desktop finance tracker that allows tertiary students to better manage their finances, cultivating good financial habits such as saving. It is optimised for CLI users so that expenses and income can be tracked efficiently by typing in commands. It is written in Java, and has a GUI created with JavaFX.
+
+### Summary of Contributions
+
+* **New feature**: Added the analytics feature to the finance tracker.
+ * What it does: It retrieves the total monthly expenses, incomes and savings over the three most recent months, calculated in the monthly budget, and displays it in the analytics tab in the form of bar charts.
+ * Justification: This feature gives the user a visualization of the quantitative information in monthly budget so that the user can view their spending and saving trends.
+ * Highlights: This feature required the use of JavaFX charts.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=w16&sort=groupTitle&sortWithin=title&since=2020-08-14&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&tabAuthor=yongping827&tabRepo=AY2021S1-CS2103T-W16-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other)
+
+* **Project management**:
+ * Created automated projects to categorize issues and keep track of the progress of each issue.
+
+* **Enhancements to existing features**:
+ * Modified how all transactions (expenses and incomes) are stored in various components of the finance tracker so that it suits the needs of each component. (Pull requests [#76](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/76), [#77](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/77), [#103](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/103), [#123](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/123))
+ * Modified the savings goal panel to show either remaining budget or budget deficit, and either current savings or savings deficit, and colored the text red if a deficit value is displayed. (Pull request [#207](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/207))
+ * Modified duplicate check for bookmark expenses and incomes. (Pull request [#228](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/228))
+ * Abstracted similar behaviour in parsers to avoid duplicate code. (Pull request [#307](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/307))
+ * Modified prefix parsing behaviour to recognize all prefixes and detect invalid ones. (Pull request [#307](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/307))
+
+* **Documentation**:
+ * User Guide:
+ * Updated `list` commands and analytics section for v1.3. (Pull request [#180](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/180))
+ * Resolved all issues pertaining to the user guide from the practical exam dry run. (Pull request [#258](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/258))
+ * Developer Guide:
+ * Updated model and storage sections. (Pull request [#143](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/143))
+ * Add Analytics feature section. (Pull request [#317](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/317))
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): [#102](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/102), [#118](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/118), [#135](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/135), [#136](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/136), [#148](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/148), [#166](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/166), [#172](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/172), [#183](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/183), [#281](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/281), [#288](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/288), [#295](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/295)
diff --git a/docs/team/zhaojj2209.md b/docs/team/zhaojj2209.md
new file mode 100644
index 00000000000..7501fb53cb6
--- /dev/null
+++ b/docs/team/zhaojj2209.md
@@ -0,0 +1,47 @@
+---
+layout: page
+title: Zhao Jingjing's Project Portfolio Page
+---
+
+## Project: Fine$$e
+
+### Overview
+
+Fine$$e is a desktop finance tracker that allows tertiary students to better manage their finances, cultivating good financial habits such as saving. It is optimised for CLI users so that expenses and income can be tracked efficiently by typing in commands. It is written in Java, and has a GUI created with JavaFX.
+
+### Summary of Contributions
+
+* **New feature**: Added the monthly budget feature to the finance tracker.
+ * What it does: With the addition of the monthly budget, the finance tracker allows the user to set their desired monthly expense limit and monthly savings goal, and calculates the user's remaining budget and current savings.
+ * Justification: This feature significantly enhances the capabilities of the finance tracker by allowing the user to track their progress in their savings, and paves the way for the application's analytics feature.
+ * Highlights: This feature required heavy use of the observer design pattern as all values in the monthly budget have to be observed by the UI so that the values and calculations can be updated immediately upon any changes.
+
+* **New feature**: Revamped the finance tracker's `find` command.
+ * What it does: Instead of only title keywords, the user can now search for transactions in the finance tracker by title keyphrase, amount, date, category, amount range and date range.
+ * Justification: This feature significantly increases the user's ease in finding the transactions they have entered into the finance tracker.
+ * Highlights: This feature involved changes in the way the find command is parsed, and different predicates for each of the search scenarios were created.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=w16&sort=groupTitle&sortWithin=title&since=2020-08-14&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&tabAuthor=zhaojj2209&tabRepo=AY2021S1-CS2103T-W16-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other)
+
+* **Project management**:
+ * Created labels for issues and pull requests on GitHub.
+ * Managed release `v1.3` (1 release) on GitHub.
+
+* **Enhancements to existing features**:
+ * Removed duplicate checks to allow duplicate transactions to be added to the finance tracker. (Pull request [#72](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/72))
+ * Restricted command inputs to allow for only one title/amount/date input in the finance tracker. (Pull request [#201](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/201))
+ * Enabled automatic tab-switching upon execution of certain commands. (Pull request [#288](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/288))
+
+* **Documentation**:
+ * User Guide:
+ * Updated user guide to reflect commands for v1.2. (Pull request [#114](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/114))
+ * Updated user guide for `find`, `set-expense-limit` and `set-savings-goal` commands for v1.3. (Pull request [#176](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/176))
+ * Developer Guide:
+ * Updated user stories and use cases in the developer guide. (Pull request [#13](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/13))
+ * Updated find transactions and monthly budget features in the developer guide. (Pull requests [#120](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/120), [#144](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/144), [#299](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/299))
+ * Updated manual testing in the developer guide. (Pull request [#299](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/299))
+
+* **Community**:
+ * PRs reviewed (with non-trivial review comments): [#39](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/39), [#48](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/48), [#90](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/90), [#107](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/107), [#132](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/132), [#255](https://github.com/AY2021S1-CS2103T-W16-3/tp/pull/255).
+
+
diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md
deleted file mode 100644
index 6907e29456c..00000000000
--- a/docs/tutorials/AddRemark.md
+++ /dev/null
@@ -1,394 +0,0 @@
----
-layout: page
-title: "Tutorial: Adding a command"
----
-
-Let's walk you through the implementation of a new command — `remark`.
-
-This command allows users of the AddressBook application to add optional remarks to people in their address book and edit it if required. The command should have the following format:
-
-`remark INDEX r/REMARK` (e.g., `remark 2 r/Likes baseball`)
-
-We’ll assume that you have already set up the development environment as outlined in the Developer’s Guide.
-
-
-## Create a new `remark` command
-
-Looking in the `logic.command` package, you will notice that each existing command have their own class. All the commands inherit from the abstract class `Command` which means that they must override `execute()`. Each `Command` returns an instance of `CommandResult` upon success and `CommandResult#feedbackToUser` is printed to the `ResultDisplay`.
-
-Let’s start by creating a new `RemarkCommand` class in the `src/main/java/seedu/address/logic/command` directory.
-
-For now, let’s keep `RemarkCommand` as simple as possible and print some output. We accomplish that by returning a `CommandResult` with an accompanying message.
-
-**`RemarkCommand.java`:**
-
-``` java
-package seedu.address.logic.commands;
-
-import seedu.address.model.Model;
-
-/**
- * Changes the remark of an existing person in the address book.
- */
-public class RemarkCommand extends Command {
-
- public static final String COMMAND_WORD = "remark";
-
- @Override
- public CommandResult execute(Model model) {
- return new CommandResult("Hello from remark");
- }
-}
-```
-
-### Hook `RemarkCommand` into the application
-
-Now that we have our `RemarkCommand` ready to be executed, we need to update `AddressBookParser#parseCommand()` to recognize the `remark` keyword. Add the new command to the `switch` block by creating a new `case` that returns a new instance of `RemarkCommand`.
-
-You can refer to the changes in this [diff](https://github.com/se-edu/addressbook-level3/commit/35eb7286f18a029d39cb7a29df8f172a001e4fd8#diff-34ace715a8a8d2e5a66e71289f017b47).
-
-### Run the application
-
-Run `Main#main` and try out your new `RemarkCommand`. If everything went well, you should see something like this:
-
-![Output displayed](../images/add-remark/RemarkHello.png)
-
-## Change `RemarkCommand` to throw an exception
-
-While we have successfully printed a message to `ResultDisplay`, the command does not do what it is supposed to do. Let’s change the command to throw an `CommandException` to accurately reflect that our command is still a work in progress.
-
-![The relationship between RemarkCommand and Command](../images/add-remark/CommandInterface.png)
-
-Following the convention in other commands, we add relevant messages as constants and use them.
-
-**`RemarkCommand.java`:**
-
-``` java
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the remark of the person identified "
- + "by the index number used in the last person listing. "
- + "Existing remark will be overwritten by the input.\n"
- + "Parameters: INDEX (must be a positive integer) "
- + "r/ [REMARK]\n"
- + "Example: " + COMMAND_WORD + " 1 "
- + "r/ Likes to swim.";
-
- public static final String MESSAGE_NOT_IMPLEMENTED_YET = "Remark command not implemented yet";
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- throw new CommandException(MESSAGE_NOT_IMPLEMENTED_YET);
- }
-```
-
-## Enhancing `RemarkCommand`
-
-Let’s change `RemarkCommand` to parse input from the user.
-
-### Make the command accept parameters
-
-We start by modifying the constructor of `RemarkCommand` to accept an `Index` and a `String`. While we are at it, let’s change the error message to echo the values. While this is not a replacement for tests, it is an obvious way to tell if our code is functioning as intended.
-
-``` java
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-//...
-public class RemarkCommand extends Command {
- //...
- public static final String MESSAGE_ARGUMENTS = "Index: %1$d, Remark: %2$s";
-
- private final Index index;
- private final String remark;
-
- /**
- * @param index of the person in the filtered person list to edit the remark
- * @param remark of the person to be updated to
- */
- public RemarkCommand(Index index, String remark) {
- requireAllNonNull(index, remark);
-
- this.index = index;
- this.remark = remark;
- }
- @Override
- public CommandResult execute(Model model) throws CommandException {
- throw new CommandException(String.format(MESSAGE_ARGUMENTS, index.getOneBased(), remark));
- }
-
- @Override
- public boolean equals(Object other) {
- // short circuit if same object
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof RemarkCommand)) {
- return false;
- }
-
- // state check
- RemarkCommand e = (RemarkCommand) other;
- return index.equals(e.index)
- && remark.equals(e.remark);
- }
-}
-```
-
-Your code should look something like [this](https://github.com/se-edu/addressbook-level3/commit/35eb7286f18a029d39cb7a29df8f172a001e4fd8#diff-34ace715a8a8d2e5a66e71289f017b47) after you are done.
-
-### Parse user input
-
-Now let’s move on to writing a parser that will extract the index and remark from the input provided by the user.
-
-Create a `RemarkCommandParser` class in the `seedu.address.logic.parser` package. The class must extend the `Parser` interface.
-
-![The relationship between Parser and RemarkCommandParser](../images/add-remark/ParserInterface.png)
-
-Thankfully, `ArgumentTokenizer#tokenize()` makes it trivial to parse user input. Let’s take a look at the JavaDoc provided for the function to understand what it does.
-
-**`ArgumentTokenizer.java`:**
-
-``` java
-/**
- * Tokenizes an arguments string and returns an {@code ArgumentMultimap}
- * object that maps prefixes to their respective argument values. Only the
- * given prefixes will be recognized in the arguments string.
- *
- * @param argsString Arguments string of the form:
- * {@code preamble value value ...}
- * @param prefixes Prefixes to tokenize the arguments string with
- * @return ArgumentMultimap object that maps prefixes to their
- * arguments
- */
-```
-
-We can tell `ArgumentTokenizer#tokenize()` to look out for our new prefix `r/` and it will return us an instance of `ArgumentMultimap`. Now let’s find out what we need to do in order to obtain the Index and String that we need. Let’s look through `ArgumentMultimap` :
-
-**`ArgumentMultimap.java`:**
-
-``` java
-/**
- * Returns the last value of {@code prefix}.
- */
-public Optional getValue(Prefix prefix) {
- List values = getAllValues(prefix);
- return values.isEmpty() ? Optional.empty() :
- Optional.of(values.get(values.size() - 1));
-}
-```
-
-This appears to be what we need to get a String of the remark. But what about the Index? Let's take a quick peek at existing `Command` that uses an index to see how it is done.
-
-**`DeleteCommandParser.java`:**
-
-``` java
-Index index = ParserUtil.parseIndex(args);
-return new DeleteCommand(index);
-```
-
-There appears to be another utility class that obtains an `Index` from the input provided by the user.
-
-Now that we have the know-how to extract the data that we need from the user’s input, we can parse the user command and create a new instance of `RemarkCommand`, as given below.
-
-**`RemarkCommandParser.java`:**
-
-``` java
-public RemarkCommand parse(String args) throws ParseException {
- requireNonNull(args);
- ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args,
- PREFIX_REMARK);
-
- Index index;
- try {
- index = ParserUtil.parseIndex(argMultimap.getPreamble());
- } catch (IllegalValueException ive) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
- RemarkCommand.MESSAGE_USAGE), ive);
- }
-
- String remark = argMultimap.getValue(PREFIX_REMARK).orElse("");
-
- return new RemarkCommand(index, remark);
-}
-```
-
-
-
-:information_source: Don’t forget to update `AddressBookParser` to use our new `RemarkCommandParser`!
-
-
-
-If you are stuck, check out the sample
-[here](https://github.com/se-edu/addressbook-level3/commit/dc6d5139d08f6403da0ec624ea32bd79a2ae0cbf#diff-fc19ecee89c3732a62fbc8c840250508).
-
-## Add `Remark` to the model
-
-Now that we have all the information that we need, let’s lay the groundwork for propagating the remarks added into the in-memory storage of person data. We achieve that by working with the `Person` model. Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the person’s name). That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a person.
-
-### Add a new `Remark` class
-
-Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code.
-
-A copy-paste and search-replace later, you should have something like [this](https://github.com/se-edu/addressbook-level3/commit/4516e099699baa9e2d51801bd26f016d812dedcc#diff-af2f075d24dfcd333876f0fbce321f25). Note how `Remark` has no constrains and thus does not require input
-validation.
-
-### Make use of `Remark`
-
-Let’s change `RemarkCommand` and `RemarkCommandParser` to use the new `Remark` class instead of plain `String`. These should be relatively simple changes.
-
-## Add a placeholder element for remark to the UI
-
-Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each person.
-
-Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-0c6b6abcfac8c205e075294f25e851fe).
-
-**`PersonCard.java`:**
-
-``` java
-@FXML
-private Label remark;
-```
-
-
-`@FXML` is an annotation that marks a private or protected field and makes it accessible to FXML. It might sound like Greek to you right now, don’t worry — we will get back to it later.
-
-Then insert the following into [`main/resources/view/PersonListCard.fxml`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-12580431f55d7880578aa4c16f249e71).
-
-**`PersonListCard.fxml`:**
-
-``` xml
-
-```
-
-That’s it! Fire up the application again and you should see something like this:
-
-![$remark shows up in each entry](../images/add-remark/$Remark.png)
-
-## Modify `Person` to support a `Remark` field
-
-Since `PersonCard` displays data from a `Person`, we need to update `Person` to get our `Remark` displayed!
-
-### Modify `Person`
-
-We change the constructor of `Person` to take a `Remark`. We will also need to define new fields and accessors accordingly to store our new addition.
-
-### Update other usages of `Person`
-
-Unfortunately, a change to `Person` will cause other commands to break, you will have to modify these commands to use the updated `Person`!
-
-
-
-:bulb: Use the `Find Usages` feature in IntelliJ IDEA on the `Person` class to find these commands.
-
-
-
-Refer to [this commit](https://github.com/se-edu/addressbook-level3/commit/ce998c37e65b92d35c91d28c7822cd139c2c0a5c) and check that you have got everything in order!
-
-
-## Updating Storage
-
-AddressBook stores data by serializing `JsonAdaptedPerson` into `json` with the help of an external library — Jackson. Let’s update `JsonAdaptedPerson` to work with our new `Person`!
-
-While the changes to code may be minimal, the test data will have to be updated as well.
-
-
-
-:exclamation: You must delete AddressBook’s storage file located at `/data/addressbook.json` before running it! Not doing so will cause AddressBook to default to an empty address book!
-
-
-
-Check out [this commit](https://github.com/se-edu/addressbook-level3/commit/556cbd0e03ff224d7a68afba171ad2eb0ce56bbf)
-to see what the changes entail.
-
-## Finalizing the UI
-
-Now that we have finalized the `Person` class and its dependencies, we can now bind the `Remark` field to the UI.
-
-Just add [this one line of code!](https://github.com/se-edu/addressbook-level3/commit/5b98fee11b6b3f5749b6b943c4f3bd3aa049b692)
-
-**`PersonCard.java`:**
-
-``` java
-public PersonCard(Person person, int displayedIndex) {
- //...
- remark.setText(person.getRemark().value);
-}
-```
-
-![The remark label is bound properly!](../images/add-remark/RemarkBound.png)
-
-## Putting everything together
-
-After the previous step, we notice a peculiar regression — we went from displaying something to nothing at all. However, this is expected behavior as we are yet to update the `RemarkCommand` to make use of the code we've been adding in the last few steps.
-
-### Update `RemarkCommand` and `RemarkCommandParser`
-
-In this last step, we modify `RemarkCommand#execute()` to change the `Remark` of a `Person`. Since all fields in a `Person` are immutable, we create a new instance of a `Person` with the values that we want and
-save it with `Model#setPerson()`.
-
-**`RemarkCommand.java`:**
-
-``` java
-//...
- public static final String MESSAGE_ADD_REMARK_SUCCESS = "Added remark to Person: %1$s";
- public static final String MESSAGE_DELETE_REMARK_SUCCESS = "Removed remark from Person: %1$s";
-//...
- @Override
- public CommandResult execute(Model model) throws CommandException {
- List lastShownList = model.getFilteredPersonList();
-
- if (index.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- }
-
- Person personToEdit = lastShownList.get(index.getZeroBased());
- Person editedPerson = new Person(personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(),
- personToEdit.getAddress(), remark, personToEdit.getTags());
-
- model.setPerson(personToEdit, editedPerson);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
-
- return new CommandResult(generateSuccessMessage(editedPerson));
- }
-
- /**
- * Generates a command execution success message based on whether the remark is added to or removed from
- * {@code personToEdit}.
- */
- private String generateSuccessMessage(Person personToEdit) {
- String message = !remark.value.isEmpty() ? MESSAGE_ADD_REMARK_SUCCESS : MESSAGE_DELETE_REMARK_SUCCESS;
- return String.format(message, personToEdit);
- }
-```
-
-![Congratulations!](../images/add-remark/RemarkComplete.png)
-
-## Writing tests
-
-Tests are crucial to ensuring that bugs don’t slip into the codebase unnoticed. This is especially true for large code bases where a change might lead to unintended behavior.
-
-Let’s verify the correctness of our code by writing some tests!
-
-### Automatically generating tests
-
-The goal is to write effective and efficient tests to ensure that `RemarkCommand#execute()` behaves as expected.
-
-The convention for test names is `methodName_testScenario_expectedResult`. An example would be
-`execute_filteredList_success`.
-
-Let’s create a test for `RemarkCommand#execute()` to test that adding a remark works. On `IntelliJ IDEA` you can bring up the context menu and choose to `Go To` \> `Test` or use the appropriate keyboard shortcut.
-
-![Using the context menu to jump to tests](../images/add-remark/ContextMenu.png)
-
-Then, create a test for the `execute` method.
-
-![Creating a test for `execute`.](../images/add-remark/CreateTest.png)
-
-Following convention, let’s change the name of the generated method to `execute_addRemarkUnfilteredList_success`.
-
-Let’s use the utility functions provided in `CommandTestUtil`. The functions ensure that commands produce the expected `CommandResult` and output the correct message. In this case, `CommandTestUtil#assertCommandSuccess` is the best fit as we are testing that a `RemarkCommand` will successfully add a `Remark`.
-
-You should end up with a test that looks something like [this](https://github.com/se-edu/addressbook-level3/commit/fac8f3fd855d55831ca0cc73313b5943d49d4d6e#diff-d749de38392f7ea504da7824641ba8d9).
-
-## Conclusion
-
-This concludes the tutorial for adding a new `Command` to AddressBook.
diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md
deleted file mode 100644
index aa8e0baaad9..00000000000
--- a/docs/tutorials/RemovingFields.md
+++ /dev/null
@@ -1,103 +0,0 @@
----
-layout: page
-title: "Tutorial: Removing Fields"
----
-
-> Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.
->
-> — Antoine de Saint-Exupery
-
-When working on AddressBook, you will most likely find that some features and fields that are no longer necessary. In scenarios like this, you can consider refactoring the existing `Person` model to suit your use case.
-
-In this tutorial, we’ll do exactly just that and remove the `address` field from `Person`.
-
-* Table of Contents
-{:toc}
-
-## Safely deleting `Address`
-
-Fortunately, IntelliJ IDEA provides a robust refactoring tool that can identify *most* usages. Let’s try to use it as much as we can.
-
-### Assisted refactoring
-
-The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu.
-
-![Usages detected](../images/remove/UnsafeDelete.png)
-
-Choose to `View Usages` and you should be presented with a list of `Safe Delete Conflicts`. These conflicts describe locations in which the `Address` class is used.
-
-![List of conflicts](../images/remove/SafeDeleteConflicts.png)
-
-Remove usages of `Address` by performing `Safe Delete`s on each entry. You will need to exercise discretion when removing usages of `Address`. Functions like `ParserUtil#parseAddress()` can be safely removed but its usages must be removed as well. Other usages like in `EditPersonDescriptor` may require more careful inspection.
-
-Let’s try removing references to `Address` in `EditPersonDescriptor`.
-
-1. Safe delete the field `address` in `EditPersonDescriptor`.
-
-1. Select `Yes` when prompted to remove getters and setters.
-
-1. Select `View Usages` again.
- ![UnsafeDeleteOnField](../images/remove/UnsafeDeleteOnField.png)
-
-1. Remove the usages of `address` and select `Do refactor` when you are done.
-
-
-
- :bulb: **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `address` field from the `Person` class will require you to modify its constructor.
-
-
-1. Repeat the steps for the remaining usages of `Address`
-
-After you are done, verify that the application still works by compiling and running it again.
-
-### Manual refactoring
-
-Unfortunately, there are usages of `Address` that IntelliJ IDEA cannot identify. You can find them by searching for instances of the word `address` in your code (`Edit` \> `Find` \> `Find in path`).
-
-Places of interest to look out for would be resources used by the application. `main/resources` contains images and `fxml` files used by the application and `test/resources` contains test data. For example, there is a `$address` in each `PersonCard` that has not been removed nor identified.
-
-![$address](../images/remove/$address.png)
-
-A quick look at the `PersonCard` class and its `fxml` file quickly reveals why it slipped past the automated refactoring.
-
-**`PersonCard.java`**
-
-``` java
-...
-@FXML
-private Label address;
-...
-```
-
-**`PersonCard.fxml`**
-
-``` xml
-...
-
-
-
-...
-```
-
-After removing the `Label`, we can proceed to formally test our code. If everything went well, you should have most of your tests pass. Fix any remaining errors until the tests all pass.
-
-## Tidying up
-
-At this point, your application is working as intended and all your tests are passing. What’s left to do is to clean up references to `Address` in test data and documentation.
-
-In `src/test/data/`, data meant for testing purposes are stored. While keeping the `address` field in the json files does not cause the tests to fail, it is not good practice to let cruft from old features accumulate.
-
-**`invalidPersonAddressBook.json`:**
-
-```json
-{
- "persons": [ {
- "name": "Person with invalid name field: Ha!ns Mu@ster",
- "phone": "9482424",
- "email": "hans@example.com",
- "address": "4th street"
- } ]
-}
-```
-
-You can go through each individual `json` file and manually remove the `address` field.
diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md
deleted file mode 100644
index bd34ed498cd..00000000000
--- a/docs/tutorials/TracingCode.md
+++ /dev/null
@@ -1,250 +0,0 @@
----
-layout: page
-title: "Tutorial: Tracing code"
----
-
-> Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. …\[Therefore,\] making it easy to read makes it easier to write.
->
-> — Robert C. Martin Clean Code: A Handbook of Agile Software Craftsmanship
-
-When trying to understand an unfamiliar code base, one common strategy used is to trace some representative execution path through the code base. One easy way to trace an execution path is to use a debugger to step through the code. In this tutorial, you will be using the IntelliJ IDEA’s debugger to trace the execution path of a specific user command.
-
-* Table of Contents
-{:toc}
-
-## Before we start
-
-Before we jump into the code, it is useful to get an idea of the overall structure and the high-level behavior of the application. This is provided in the 'Architecture' section of the developer guide. In particular, the architecture diagram (reproduced below), tells us that the App consists of several components.
-
-![ArchitectureDiagram](../images/ArchitectureDiagram.png)
-
-It also has a sequence diagram (reproduced below) that tells us how a command propagates through the App.
-
-![Architecture sequence diagram from the developer
-guide](../images/ArchitectureSequenceDiagram.png)
-
-Note how the diagram shows only how the execution flows *between* the main components. That is, it does not show details of the execution path *inside* each component. By hiding those details, the diagram succeeds in informing the reader about the overall execution path of a command without overwhelming the reader with too much details. In this tutorial, you aim to find those omitted details so that you get a more in-depth understanding of the code base.
-
-Before we proceed, ensure that you have done the following:
-1. Read the [*Architecture* section of the DG](../DeveloperGuide.md#architecture)
-1. Set up the project in Intellij IDEA
-1. Learn basic debugging features of Intellij IDEA
-
-## Setting a break point
-
-As you know, the first step of debugging is to put in a breakpoint where you want the debugger to pause the execution. For example, if you are trying to understand how the App starts up, you would put a breakpoint in the first statement of the `main` method. In our case, we would want to begin the tracing at the very point where the App start processing user input (i.e., somewhere in the UI component), and then trace through how the execution proceeds through the UI component. However, the execution path through a GUI is often somewhat obscure due to various *event-driven mechanisms* used by GUI frameworks, which happens to be the case here too. Therefore, let us put the breakpoint where the UI transfers control to the Logic component. According to the sequence diagram, the UI component yields control to the Logic component through a method named `execute`. Searching through the code base for `execute()` yields a promising candidate in `seedu.address.ui.CommandBox.CommandExecutor`.
-
-![Using the `Search for target by name` feature. `Navigate` \> `Symbol`.](../images/tracing/Execute.png)
-
-A quick look at the class confirms that this is indeed close to what we’re looking for. However, it is just an `Interface`. Let’s delve further and find the implementation of the interface by using the `Find Usages` feature in IntelliJ IDEA.
-
-![`Find Usages` tool window. `Edit` \> `Find` \> `Find Usages`.](../images/tracing/FindUsages.png)
-
-Bingo\! `MainWindow#executeCommand()` seems to be exactly what we’re looking for\!
-
-Now let’s set the breakpoint. First, double-click the item to reach the corresponding code. Once there, click on the left gutter to set a breakpoint, as shown below.
- ![LeftGutter](../images/tracing/LeftGutter.png)
-
-## Tracing the execution path
-
-Recall from the User Guide that the `edit` command has the format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…` For this tutorial we will be issuing the command `edit 1 n/Alice Yeoh`.
-
-
-
-:bulb: **Tip:** Over the course of the debugging session, you will encounter every major component in the application. Try to jot down what happens inside the component and where the execution transfers to another component.
-
-
-1. To start the debugging session, simply `Run` \> `Debug Main`
-
-1. Enter `edit 1 n/Alice Yeoh` into the command box and press `Enter`.
-
-1. The Debugger tool window should show up and look something like this:
- ![DebuggerStep1](../images/tracing/DebuggerStep1.png)
-
-1. Use the `Show execution point` feature to jump to the line of code that we stopped at:
- ![ShowExecutionPoint](../images/tracing/ShowExecutionPoint.png)
-
-1. `CommandResult commandResult = logic.execute(commandText);` is the line that you end up at.
-
-1. We are interested in the `logic.execute(commandText)` portion of that line so let’s `Step in` into that method call:
- ![StepInto](../images/tracing/StepInto.png)
-
-1. We end up in `LogicManager#execute()`. Let’s take a look at the body of the method and annotate what we can deduce.
-
- **LogicManager\#execute().**
-
- ``` java
- @Override
- public CommandResult execute(String commandText)
- throws CommandException, ParseException {
-
- //Logging, safe to ignore
- logger.info("----------------[USER COMMAND][" + commandText + "]");
-
- CommandResult commandResult;
- //Parse user input from String to a Command
- Command command = addressBookParser.parseCommand(commandText);
- //Executes the Command and stores the result
- commandResult = command.execute(model);
-
- try {
- //We can deduce that the previous line of code modifies model in some way
- // since it's being stored here.
- storage.saveAddressBook(model.getAddressBook());
- } catch (IOException ioe) {
- throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
- }
-
- return commandResult;
- }
- ```
-
-1. `LogicManager#execute()` appears to delegate most of the heavy lifting to other components. Let’s take a closer look at each one.
-
-1. `Step over` the logging code since it is of no interest to us now. ![StepOver](../images/tracing/StepOver.png)
-
-1. `Step into` the line where user input in parsed from a String to a Command.
-
- **`AddressBookParser\#parseCommand()`**
-
- ``` java
- public Command parseCommand(String userInput) throws ParseException {
- ...
- final String commandWord = matcher.group("commandWord");
- final String arguments = matcher.group("arguments");
- ...
- ```
-
-1. `Step over` until you reach the `switch` statement. The `Variables` window now shows the value of both `commandWord` and `arguments`:
- ![Variables](../images/tracing/Variables.png)
-
-1. We see that the value of `commandWord` is now `edit` but `arguments` is still not processed in any meaningful way.
-
-1. Stepping into the `switch`, we obviously stop at **`AddressBookParser\#parseCommand()`.**
-
- ``` java
- ...
- case EditCommand.COMMAND_WORD:
- return new EditCommandParser().parse(arguments);
- ...
- ```
-
-1. Let’s see what `EditCommandParser#parse()` does by stepping into it.
-
-1. Stepping through the method shows that it calls `ArgumentTokenizer#tokenize()` and `ParserUtil#parseIndex()` to obtain the arguments and index required.
-
-
:bulb: **Tip:** Sometimes you might end up stepping into functions that are not of interest. Simply `step out` of them\!
-
-
-1. The rest of the method seems to exhaustively check for the existence of each possible parameter of the `edit` command and store any possible changes in an `EditPersonDescriptor`. Recall that we can verify the contents of `editPersonDesciptor` through the `Variable` tool window.
- ![EditCommand](../images/tracing/EditCommand.png)
-
-1. Let’s continue stepping through until we return to `LogicManager#execute()`.
-
- The sequence diagram below shows the details of the execution path through the Logic component. Does the execution path you traced in the code so far matches with the diagram?
- ![Tracing an `edit` command through the Logic component](../images/tracing/LogicSequenceDiagram.png)
-
-1. Now let’s see what happens when we call `command#execute()`\!
-
- **`EditCommand\#execute()`:**
-
- ``` java
- @Override
- public CommandResult execute(Model model) throws CommandException {
- ...
- Person personToEdit = lastShownList.get(index.getZeroBased());
- Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
- if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
- throw new CommandException(MESSAGE_DUPLICATE_PERSON);
- }
- model.setPerson(personToEdit, editedPerson);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson));
- }
- ```
-
-1. As suspected, `command#execute()` does indeed make changes to `model`.
-
-1. We can a closer look at how storage works by repeatedly stepping into the code until we arrive at
- `JsonAddressBook#saveAddressBook()`.
-
-1. Again, it appears that the heavy lifting is delegated. Let’s take a look at `JsonSerializableAddressBook`'s constructor.
-
- **`JsonSerializableAddressBook\#JsonSerializableAddressBook()`:**
-
- ``` java
- /**
- * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use.
- *
- * @param source future changes to this will not affect the created
- * {@code JsonSerializableAddressBook}.
- */
- public JsonSerializableAddressBook(ReadOnlyAddressBook source) {
- persons.addAll(
- source.getPersonList()
- .stream()
- .map(JsonAdaptedPerson::new)
- .collect(Collectors.toList()));
- }
- ```
-
-1. It appears that a `JsonAdaptedPerson` is created for each `Person` and then added to the `JsonSerializableAddressBook`.
-
-1. We can continue to step through until we return to `MainWindow#executeCommand()`.
-
-1. Stepping into `resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());`, we end up in:
-
- **`ResultDisplay\#setFeedbackToUser()`**
-
- ``` java
- public void setFeedbackToUser(String feedbackToUser) {
- requireNonNull(feedbackToUser);
- resultDisplay.setText(feedbackToUser);
- }
- ```
-
-1. Finally, we step through until we reach the end of
- `MainWindow#executeCommand()`.
-
-## Conclusion
-
-In this tutorial, we traced a valid edit command from raw user input to
-the result being displayed to the user. From this tutorial, you learned
-more about the inner workings of AddressBook and how the various
-components mesh together to form one cohesive product.
-
-Here are some quick questions you can try to answer based on your
-execution path tracing. In some cases, you can do further tracing for
-the given commands to find exactly what happens.
-
-1. In this tutorial, we traced the "happy path" (i.e., no errors). What
- do you think will happen if we traced the following commands
- instead? What exceptions do you think will be thrown(if any), where
- will the exceptions be thrown and where will they be handled?
-
- 1. `redit 1 n/Alice Yu`
-
- 2. `edit 0 n/Alice Yu`
-
- 3. `edit 1 n/Alex Yeoh`
-
- 4. `edit 1`
-
- 5. `edit 1 n/アリス ユー`
-
- 6. `edit 1 t/one t/two t/three t/one`
-
-2. What components will you have to modify to perform the following
- enhancements to the application?
-
- 1. Make command words case-insensitive
-
- 2. Allow `delete` to remove more than one index at a time
-
- 3. Save the address book in the CSV format instead
-
- 4. Add a new command
-
- 5. Add a new field to `Person`
-
- 6. Add a new entity to the address book
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 44e7c4d1d7b..12d38de6a48 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/AppParameters.java
similarity index 91%
rename from src/main/java/seedu/address/AppParameters.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/AppParameters.java
index ab552c398f3..b37bd154f1a 100644
--- a/src/main/java/seedu/address/AppParameters.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/AppParameters.java
@@ -1,4 +1,4 @@
-package seedu.address;
+package ay2021s1_cs2103_w16_3.finesse;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -6,9 +6,9 @@
import java.util.Objects;
import java.util.logging.Logger;
+import ay2021s1_cs2103_w16_3.finesse.commons.core.LogsCenter;
+import ay2021s1_cs2103_w16_3.finesse.commons.util.FileUtil;
import javafx.application.Application;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.util.FileUtil;
/**
* Represents the parsed command-line parameters given to the application.
diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/Main.java
similarity index 95%
rename from src/main/java/seedu/address/Main.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/Main.java
index 052a5068631..1469860c67a 100644
--- a/src/main/java/seedu/address/Main.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/Main.java
@@ -1,4 +1,4 @@
-package seedu.address;
+package ay2021s1_cs2103_w16_3.finesse;
import javafx.application.Application;
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/MainApp.java
similarity index 62%
rename from src/main/java/seedu/address/MainApp.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/MainApp.java
index e5cfb161b73..0934c1683cc 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/MainApp.java
@@ -1,35 +1,36 @@
-package seedu.address;
+package ay2021s1_cs2103_w16_3.finesse;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.logging.Logger;
+import ay2021s1_cs2103_w16_3.finesse.commons.core.Config;
+import ay2021s1_cs2103_w16_3.finesse.commons.core.LogsCenter;
+import ay2021s1_cs2103_w16_3.finesse.commons.core.Version;
+import ay2021s1_cs2103_w16_3.finesse.commons.exceptions.DataConversionException;
+import ay2021s1_cs2103_w16_3.finesse.commons.util.ConfigUtil;
+import ay2021s1_cs2103_w16_3.finesse.commons.util.StringUtil;
+import ay2021s1_cs2103_w16_3.finesse.logic.Logic;
+import ay2021s1_cs2103_w16_3.finesse.logic.LogicManager;
+import ay2021s1_cs2103_w16_3.finesse.model.FinanceTracker;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.ModelManager;
+import ay2021s1_cs2103_w16_3.finesse.model.ReadOnlyFinanceTracker;
+import ay2021s1_cs2103_w16_3.finesse.model.ReadOnlyUserPrefs;
+import ay2021s1_cs2103_w16_3.finesse.model.UserPrefs;
+import ay2021s1_cs2103_w16_3.finesse.model.util.SampleDataUtil;
+import ay2021s1_cs2103_w16_3.finesse.storage.FinanceTrackerStorage;
+import ay2021s1_cs2103_w16_3.finesse.storage.JsonFinanceTrackerStorage;
+import ay2021s1_cs2103_w16_3.finesse.storage.JsonUserPrefsStorage;
+import ay2021s1_cs2103_w16_3.finesse.storage.Storage;
+import ay2021s1_cs2103_w16_3.finesse.storage.StorageManager;
+import ay2021s1_cs2103_w16_3.finesse.storage.UserPrefsStorage;
+import ay2021s1_cs2103_w16_3.finesse.ui.Ui;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiManager;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState;
import javafx.application.Application;
import javafx.stage.Stage;
-import seedu.address.commons.core.Config;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.core.Version;
-import seedu.address.commons.exceptions.DataConversionException;
-import seedu.address.commons.util.ConfigUtil;
-import seedu.address.commons.util.StringUtil;
-import seedu.address.logic.Logic;
-import seedu.address.logic.LogicManager;
-import seedu.address.model.AddressBook;
-import seedu.address.model.Model;
-import seedu.address.model.ModelManager;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
-import seedu.address.model.util.SampleDataUtil;
-import seedu.address.storage.AddressBookStorage;
-import seedu.address.storage.JsonAddressBookStorage;
-import seedu.address.storage.JsonUserPrefsStorage;
-import seedu.address.storage.Storage;
-import seedu.address.storage.StorageManager;
-import seedu.address.storage.UserPrefsStorage;
-import seedu.address.ui.Ui;
-import seedu.address.ui.UiManager;
/**
* Runs the application.
@@ -41,6 +42,7 @@ public class MainApp extends Application {
private static final Logger logger = LogsCenter.getLogger(MainApp.class);
protected Ui ui;
+ protected UiState uiState;
protected Logic logic;
protected Storage storage;
protected Model model;
@@ -48,7 +50,7 @@ public class MainApp extends Application {
@Override
public void init() throws Exception {
- logger.info("=============================[ Initializing AddressBook ]===========================");
+ logger.info("=============================[ Initializing Fine$$e ]===========================");
super.init();
AppParameters appParameters = AppParameters.parse(getParameters());
@@ -56,8 +58,9 @@ public void init() throws Exception {
UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath());
UserPrefs userPrefs = initPrefs(userPrefsStorage);
- AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath());
- storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ FinanceTrackerStorage financeTrackerStorage =
+ new JsonFinanceTrackerStorage(userPrefs.getFinanceTrackerFilePath());
+ storage = new StorageManager(financeTrackerStorage, userPrefsStorage);
initLogging(config);
@@ -65,29 +68,31 @@ public void init() throws Exception {
logic = new LogicManager(model, storage);
- ui = new UiManager(logic);
+ uiState = new UiState();
+
+ ui = new UiManager(model, logic, uiState);
}
/**
- * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found,
- * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book.
+ * Returns a {@code ModelManager} with the data from {@code storage}'s finance tracker and {@code userPrefs}.
+ * The data from the sample finance tracker will be used instead if {@code storage}'s finance tracker is not found,
+ * or an empty finance tracker will be used instead if errors occur when reading {@code storage}'s finance tracker.
*/
private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
- Optional addressBookOptional;
- ReadOnlyAddressBook initialData;
+ Optional financeTrackerOptional;
+ ReadOnlyFinanceTracker initialData;
try {
- addressBookOptional = storage.readAddressBook();
- if (!addressBookOptional.isPresent()) {
- logger.info("Data file not found. Will be starting with a sample AddressBook");
+ financeTrackerOptional = storage.readFinanceTracker();
+ if (!financeTrackerOptional.isPresent()) {
+ logger.info("Data file not found. Will be starting with a sample FinanceTracker");
}
- initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook);
+ initialData = financeTrackerOptional.orElseGet(SampleDataUtil::getSampleFinanceTracker);
} catch (DataConversionException e) {
- logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook");
- initialData = new AddressBook();
+ logger.warning("Data file not in the correct format. Will be starting with an empty FinanceTracker");
+ initialData = new FinanceTracker();
} catch (IOException e) {
- logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook");
- initialData = new AddressBook();
+ logger.warning("Problem while reading from the file. Will be starting with an empty FinanceTracker");
+ initialData = new FinanceTracker();
}
return new ModelManager(initialData, userPrefs);
@@ -151,7 +156,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
+ "Using default user prefs");
initializedPrefs = new UserPrefs();
} catch (IOException e) {
- logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook");
+ logger.warning("Problem while reading from the file. Will be starting with an empty FinanceTracker");
initializedPrefs = new UserPrefs();
}
@@ -167,13 +172,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
@Override
public void start(Stage primaryStage) {
- logger.info("Starting AddressBook " + MainApp.VERSION);
+ logger.info("Starting Fine$$e " + MainApp.VERSION);
ui.start(primaryStage);
}
@Override
public void stop() {
- logger.info("============================ [ Stopping Address Book ] =============================");
+ logger.info("============================ [ Stopping Fine$$e ] =============================");
try {
storage.saveUserPrefs(model.getUserPrefs());
} catch (IOException e) {
diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/Config.java
similarity index 96%
rename from src/main/java/seedu/address/commons/core/Config.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/Config.java
index 91145745521..8525a65b836 100644
--- a/src/main/java/seedu/address/commons/core/Config.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/Config.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package ay2021s1_cs2103_w16_3.finesse.commons.core;
import java.nio.file.Path;
import java.nio.file.Paths;
diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/GuiSettings.java
similarity index 68%
rename from src/main/java/seedu/address/commons/core/GuiSettings.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/GuiSettings.java
index ba33653be67..81c8a5167ce 100644
--- a/src/main/java/seedu/address/commons/core/GuiSettings.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/GuiSettings.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package ay2021s1_cs2103_w16_3.finesse.commons.core;
import java.awt.Point;
import java.io.Serializable;
@@ -10,12 +10,14 @@
*/
public class GuiSettings implements Serializable {
- private static final double DEFAULT_HEIGHT = 600;
- private static final double DEFAULT_WIDTH = 740;
+ private static final double DEFAULT_HEIGHT = 815.0;
+ private static final double DEFAULT_WIDTH = 1240.0;
+ private static final boolean DEFAULT_FULLSCREEN = false;
private final double windowWidth;
private final double windowHeight;
private final Point windowCoordinates;
+ private final boolean isFullscreen;
/**
* Constructs a {@code GuiSettings} with the default height, width and position.
@@ -24,15 +26,17 @@ public GuiSettings() {
windowWidth = DEFAULT_WIDTH;
windowHeight = DEFAULT_HEIGHT;
windowCoordinates = null; // null represent no coordinates
+ isFullscreen = DEFAULT_FULLSCREEN;
}
/**
- * Constructs a {@code GuiSettings} with the specified height, width and position.
+ * Constructs a {@code GuiSettings} with the specified height, width, position, and whether it is fullscreen.
*/
- public GuiSettings(double windowWidth, double windowHeight, int xPosition, int yPosition) {
+ public GuiSettings(double windowWidth, double windowHeight, int xPosition, int yPosition, boolean isFullscreen) {
this.windowWidth = windowWidth;
this.windowHeight = windowHeight;
windowCoordinates = new Point(xPosition, yPosition);
+ this.isFullscreen = isFullscreen;
}
public double getWindowWidth() {
@@ -47,6 +51,10 @@ public Point getWindowCoordinates() {
return windowCoordinates != null ? new Point(windowCoordinates) : null;
}
+ public boolean isFullscreen() {
+ return isFullscreen;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -60,20 +68,22 @@ public boolean equals(Object other) {
return windowWidth == o.windowWidth
&& windowHeight == o.windowHeight
- && Objects.equals(windowCoordinates, o.windowCoordinates);
+ && Objects.equals(windowCoordinates, o.windowCoordinates)
+ && isFullscreen == o.isFullscreen;
}
@Override
public int hashCode() {
- return Objects.hash(windowWidth, windowHeight, windowCoordinates);
+ return Objects.hash(windowWidth, windowHeight, windowCoordinates, isFullscreen);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
- sb.append("Width : " + windowWidth + "\n");
- sb.append("Height : " + windowHeight + "\n");
- sb.append("Position : " + windowCoordinates);
+ sb.append("Width: " + windowWidth + "\n");
+ sb.append("Height: " + windowHeight + "\n");
+ sb.append("Position: " + windowCoordinates + "\n");
+ sb.append("Is Fullscreen: " + (isFullscreen ? "Yes" : "No"));
return sb.toString();
}
}
diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/LogsCenter.java
similarity index 97%
rename from src/main/java/seedu/address/commons/core/LogsCenter.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/LogsCenter.java
index 431e7185e76..9d561849091 100644
--- a/src/main/java/seedu/address/commons/core/LogsCenter.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/LogsCenter.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package ay2021s1_cs2103_w16_3.finesse.commons.core;
import java.io.IOException;
import java.util.Arrays;
@@ -18,7 +18,7 @@
public class LogsCenter {
private static final int MAX_FILE_COUNT = 5;
private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB
- private static final String LOG_FILE = "addressbook.log";
+ private static final String LOG_FILE = "fine$$e.log";
private static Level currentLogLevel = Level.INFO;
private static final Logger logger = LogsCenter.getLogger(LogsCenter.class);
private static FileHandler fileHandler;
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/Messages.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/Messages.java
new file mode 100644
index 00000000000..3a763d41437
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/Messages.java
@@ -0,0 +1,30 @@
+package ay2021s1_cs2103_w16_3.finesse.commons.core;
+
+/**
+ * Container for user visible messages.
+ */
+public class Messages {
+
+ public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command.";
+ public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
+ public static final String MESSAGE_INVALID_TAB_FORMAT = "'%s' command can only be used in the following tabs: %s.";
+ public static final String MESSAGE_INVALID_BOOKMARK_EXPENSE_DISPLAYED_INDEX =
+ "The bookmark expense index provided is invalid.";
+ public static final String MESSAGE_INVALID_BOOKMARK_INCOME_DISPLAYED_INDEX =
+ "The bookmark income index provided is invalid.";
+ public static final String MESSAGE_INVALID_EXPENSE_DISPLAYED_INDEX =
+ "The expense index provided is invalid.";
+ public static final String MESSAGE_INVALID_INCOME_DISPLAYED_INDEX =
+ "The income index provided is invalid.";
+ public static final String MESSAGE_TRANSACTIONS_LISTED_OVERVIEW = "%1$d transactions listed!";
+ public static final String MESSAGE_EXPENSES_LISTED_OVERVIEW = "%1$d expenses listed!";
+ public static final String MESSAGE_INCOMES_LISTED_OVERVIEW = "%1$d incomes listed!";
+ public static final String MESSAGE_METHOD_SHOULD_NOT_BE_CALLED = "This method should not be called.";
+ public static final String MESSAGE_NO_ARGUMENTS_COMMAND_FORMAT = "'%s' command cannot have any arguments.";
+ public static final String MESSAGE_INVALID_PREFIX_PRESENT_HEADER = "These invalid prefixes were found:";
+
+ /**
+ * Prevents instantiation of this class.
+ */
+ private Messages() {}
+}
diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/Version.java
similarity index 98%
rename from src/main/java/seedu/address/commons/core/Version.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/Version.java
index 12142ec1e32..4f117976e2c 100644
--- a/src/main/java/seedu/address/commons/core/Version.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/Version.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core;
+package ay2021s1_cs2103_w16_3.finesse.commons.core;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/index/Index.java
similarity index 96%
rename from src/main/java/seedu/address/commons/core/index/Index.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/index/Index.java
index 19536439c09..aedbce75d6f 100644
--- a/src/main/java/seedu/address/commons/core/index/Index.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/core/index/Index.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.core.index;
+package ay2021s1_cs2103_w16_3.finesse.commons.core.index;
/**
* Represents a zero-based or one-based index.
diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/exceptions/DataConversionException.java
similarity index 79%
rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/exceptions/DataConversionException.java
index 1f689bd8e3f..0206ae2d0df 100644
--- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/exceptions/DataConversionException.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.exceptions;
+package ay2021s1_cs2103_w16_3.finesse.commons.exceptions;
/**
* Represents an error during conversion of data from one format to another
diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/exceptions/IllegalValueException.java
similarity index 90%
rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/exceptions/IllegalValueException.java
index 19124db485c..0048ed4b33a 100644
--- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/exceptions/IllegalValueException.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.exceptions;
+package ay2021s1_cs2103_w16_3.finesse.commons.exceptions;
/**
* Signals that some given data does not fulfill some constraints.
diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/AppUtil.java
similarity index 92%
rename from src/main/java/seedu/address/commons/util/AppUtil.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/AppUtil.java
index 87aa89c0326..8e0d05f19c6 100644
--- a/src/main/java/seedu/address/commons/util/AppUtil.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/AppUtil.java
@@ -1,9 +1,9 @@
-package seedu.address.commons.util;
+package ay2021s1_cs2103_w16_3.finesse.commons.util;
import static java.util.Objects.requireNonNull;
+import ay2021s1_cs2103_w16_3.finesse.MainApp;
import javafx.scene.image.Image;
-import seedu.address.MainApp;
/**
* A container for App specific utility functions
diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/CollectionUtil.java
similarity index 94%
rename from src/main/java/seedu/address/commons/util/CollectionUtil.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/CollectionUtil.java
index eafe4dfd681..d6d760adc19 100644
--- a/src/main/java/seedu/address/commons/util/CollectionUtil.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/CollectionUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package ay2021s1_cs2103_w16_3.finesse.commons.util;
import static java.util.Objects.requireNonNull;
diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/ConfigUtil.java
similarity index 72%
rename from src/main/java/seedu/address/commons/util/ConfigUtil.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/ConfigUtil.java
index f7f8a2bd44c..f1d2bdbeff2 100644
--- a/src/main/java/seedu/address/commons/util/ConfigUtil.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/ConfigUtil.java
@@ -1,11 +1,11 @@
-package seedu.address.commons.util;
+package ay2021s1_cs2103_w16_3.finesse.commons.util;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
-import seedu.address.commons.core.Config;
-import seedu.address.commons.exceptions.DataConversionException;
+import ay2021s1_cs2103_w16_3.finesse.commons.core.Config;
+import ay2021s1_cs2103_w16_3.finesse.commons.exceptions.DataConversionException;
/**
* A class for accessing the Config File.
diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/FileUtil.java
similarity index 97%
rename from src/main/java/seedu/address/commons/util/FileUtil.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/FileUtil.java
index b1e2767cdd9..8abd5c20b15 100644
--- a/src/main/java/seedu/address/commons/util/FileUtil.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/FileUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package ay2021s1_cs2103_w16_3.finesse.commons.util;
import java.io.IOException;
import java.nio.file.Files;
diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/JsonUtil.java
similarity index 96%
rename from src/main/java/seedu/address/commons/util/JsonUtil.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/JsonUtil.java
index 8ef609f055d..8ccbd296163 100644
--- a/src/main/java/seedu/address/commons/util/JsonUtil.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/JsonUtil.java
@@ -1,4 +1,4 @@
-package seedu.address.commons.util;
+package ay2021s1_cs2103_w16_3.finesse.commons.util;
import static java.util.Objects.requireNonNull;
@@ -20,8 +20,8 @@
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.commons.exceptions.DataConversionException;
+import ay2021s1_cs2103_w16_3.finesse.commons.core.LogsCenter;
+import ay2021s1_cs2103_w16_3.finesse.commons.exceptions.DataConversionException;
/**
* Converts a Java object instance to JSON and vice versa
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/StringUtil.java
similarity index 53%
rename from src/main/java/seedu/address/commons/util/StringUtil.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/StringUtil.java
index 61cc8c9a1cb..54b027b9348 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/commons/util/StringUtil.java
@@ -1,11 +1,10 @@
-package seedu.address.commons.util;
+package ay2021s1_cs2103_w16_3.finesse.commons.util;
+import static ay2021s1_cs2103_w16_3.finesse.commons.util.AppUtil.checkArgument;
import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.util.Arrays;
/**
* Helper functions for handling strings.
@@ -13,33 +12,36 @@
public class StringUtil {
/**
- * Returns true if the {@code sentence} contains the {@code word}.
- * Ignores case, but a full word match is required.
+ * Returns true if the {@code sentence} contains the {@code keyphrase}.
+ * Ignores case, but a full keyphrase match is required.
* examples:
- * containsWordIgnoreCase("ABc def", "abc") == true
- * containsWordIgnoreCase("ABc def", "DEF") == true
- * containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
+ * containsIgnoreCase("ABc def", "abc") == true
+ * containsIgnoreCase("ABc def", "DEF") == true
+ * containsIgnoreCase("ABc def", "AB") == false //not a full keyphrase match
*
* @param sentence cannot be null
- * @param word cannot be null, cannot be empty, must be a single word
+ * @param keyphrase cannot be null, cannot be empty
*/
- public static boolean containsWordIgnoreCase(String sentence, String word) {
+ public static boolean containsIgnoreCase(String sentence, String keyphrase) {
requireNonNull(sentence);
- requireNonNull(word);
+ checkArgument(!isEmptyString(keyphrase), "Keyphrase cannot be empty");
- String preppedWord = word.trim();
- checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty");
- checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word");
-
- String preppedSentence = sentence;
- String[] wordsInPreppedSentence = preppedSentence.split("\\s+");
+ String preppedWord = keyphrase.trim();
+ return sentence.toLowerCase().contains(preppedWord.toLowerCase());
+ }
- return Arrays.stream(wordsInPreppedSentence)
- .anyMatch(preppedWord::equalsIgnoreCase);
+ /**
+ * Checks if the string {@code s} is an empty string.
+ * A string is considered empty if it is the empty string, or is a string consisting of only whitespaces.
+ * @param s cannot be null
+ */
+ public static boolean isEmptyString(String s) {
+ requireNonNull(s);
+ return s.trim().isEmpty();
}
/**
- * Returns a detailed message of the t, including the stack trace.
+ * Returns a detailed message of the Throwable object {@code t}, including the stack trace.
*/
public static String getDetails(Throwable t) {
requireNonNull(t);
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/Logic.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/Logic.java
new file mode 100644
index 00000000000..58a2c96435e
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/Logic.java
@@ -0,0 +1,78 @@
+package ay2021s1_cs2103_w16_3.finesse.logic;
+
+import java.nio.file.Path;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.GuiSettings;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.CommandResult;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.logic.time.exceptions.TemporalException;
+import ay2021s1_cs2103_w16_3.finesse.model.ReadOnlyFinanceTracker;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkExpense;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkIncome;
+import ay2021s1_cs2103_w16_3.finesse.model.budget.MonthlyBudget;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Expense;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Income;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Transaction;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState;
+import javafx.collections.ObservableList;
+
+/**
+ * API of the Logic component.
+ */
+public interface Logic {
+ /**
+ * Executes the command and returns the result.
+ *
+ * @param commandText The command as entered by the user.
+ * @param uiState The current state of the UI.
+ * @return The result of the command execution.
+ * @throws CommandException If an error occurs during command execution.
+ * @throws ParseException If an error occurs during parsing.
+ * @throws TemporalException If irregularities in the system time are detected.
+ */
+ CommandResult execute(String commandText, UiState uiState)
+ throws CommandException, ParseException, TemporalException;
+
+ /** Calculates the budget information in the finance tracker. */
+ void calculateBudgetInfo();
+
+ /**
+ * Returns the FinanceTracker.
+ *
+ * @see ay2021s1_cs2103_w16_3.finesse.model.Model#getFinanceTracker()
+ */
+ ReadOnlyFinanceTracker getFinanceTracker();
+
+ /** Returns an unmodifiable view of the filtered list of transactions. */
+ ObservableList getFilteredTransactionList();
+
+ /** Returns an unmodifiable view of the filtered list of expenses. */
+ ObservableList getFilteredExpenseList();
+
+ /** Returns an unmodifiable view of the filtered list of incomes. */
+ ObservableList getFilteredIncomeList();
+
+ /** Returns an unmodifiable view of the filtered list of bookmark expenses. */
+ ObservableList getFilteredBookmarkExpenseList();
+
+ /** Returns an unmodifiable view of the filtered list of bookmark incomes. */
+ ObservableList getFilteredBookmarkIncomeList();
+ /** Returns the monthly budget. */
+ MonthlyBudget getMonthlyBudget();
+
+ /**
+ * Returns the user prefs' finance tracker file path.
+ */
+ Path getFinanceTrackerFilePath();
+
+ /**
+ * Returns the user prefs' GUI settings.
+ */
+ GuiSettings getGuiSettings();
+
+ /**
+ * Set the user prefs' GUI settings.
+ */
+ void setGuiSettings(GuiSettings guiSettings);
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/LogicManager.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/LogicManager.java
new file mode 100644
index 00000000000..aa724f1aaf9
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/LogicManager.java
@@ -0,0 +1,123 @@
+package ay2021s1_cs2103_w16_3.finesse.logic;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.logging.Logger;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.GuiSettings;
+import ay2021s1_cs2103_w16_3.finesse.commons.core.LogsCenter;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.Command;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.CommandResult;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.FinanceTrackerParser;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.logic.time.Timekeeper;
+import ay2021s1_cs2103_w16_3.finesse.logic.time.exceptions.TemporalException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.ReadOnlyFinanceTracker;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkExpense;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkIncome;
+import ay2021s1_cs2103_w16_3.finesse.model.budget.MonthlyBudget;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Expense;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Income;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Transaction;
+import ay2021s1_cs2103_w16_3.finesse.storage.Storage;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState;
+import javafx.collections.ObservableList;
+
+/**
+ * The main LogicManager of the app.
+ */
+public class LogicManager implements Logic {
+ public static final String FILE_OPS_ERROR_MESSAGE = "Could not save data to file: ";
+ private final Logger logger = LogsCenter.getLogger(LogicManager.class);
+
+ private final Model model;
+ private final Storage storage;
+ private final FinanceTrackerParser financeTrackerParser;
+ private final Timekeeper timekeeper;
+
+ /**
+ * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}.
+ */
+ public LogicManager(Model model, Storage storage) {
+ this.model = model;
+ this.storage = storage;
+ financeTrackerParser = new FinanceTrackerParser();
+ timekeeper = new Timekeeper();
+ }
+
+ @Override
+ public CommandResult execute(String commandText, UiState uiState)
+ throws CommandException, ParseException, TemporalException {
+ logger.info("----------------[USER COMMAND][" + commandText + "]");
+
+ timekeeper.checkIn();
+
+ CommandResult commandResult;
+ Command command = financeTrackerParser.parseCommand(commandText, uiState);
+ commandResult = command.execute(model);
+
+ try {
+ storage.saveFinanceTracker(model.getFinanceTracker());
+ } catch (IOException ioe) {
+ throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
+ }
+
+ return commandResult;
+ }
+
+ @Override
+ public void calculateBudgetInfo() {
+ model.calculateBudgetInfo();
+ }
+
+ @Override
+ public ReadOnlyFinanceTracker getFinanceTracker() {
+ return model.getFinanceTracker();
+ }
+
+ @Override
+ public ObservableList getFilteredTransactionList() {
+ return model.getFilteredTransactionList();
+ }
+
+ @Override
+ public ObservableList getFilteredExpenseList() {
+ return model.getFilteredExpenseList();
+ }
+
+ @Override
+ public ObservableList getFilteredIncomeList() {
+ return model.getFilteredIncomeList();
+ }
+
+ @Override
+ public ObservableList getFilteredBookmarkExpenseList() {
+ return model.getFilteredBookmarkExpenseList();
+ }
+
+ @Override
+ public ObservableList getFilteredBookmarkIncomeList() {
+ return model.getFilteredBookmarkIncomeList();
+ }
+
+ public MonthlyBudget getMonthlyBudget() {
+ return model.getMonthlyBudget();
+ }
+
+ @Override
+ public Path getFinanceTrackerFilePath() {
+ return model.getFinanceTrackerFilePath();
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ return model.getGuiSettings();
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ model.setGuiSettings(guiSettings);
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/AddExpenseCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/AddExpenseCommand.java
new file mode 100644
index 00000000000..9a53e705cab
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/AddExpenseCommand.java
@@ -0,0 +1,60 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_CATEGORY;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_DATE;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_TITLE;
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Expense;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState.Tab;
+
+/**
+ * Adds an expense to the finance tracker.
+ */
+public class AddExpenseCommand extends Command {
+
+ public static final String COMMAND_WORD = "add-expense";
+ public static final String COMMAND_ALIAS = "adde";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an expense to the finance tracker. "
+ + "Parameters: "
+ + PREFIX_TITLE + "TITLE "
+ + PREFIX_AMOUNT + "AMOUNT "
+ + "[" + PREFIX_DATE + "DATE] "
+ + "[" + PREFIX_CATEGORY + "CATEGORY]...\n"
+ + "Inputting the date is optional. If no input is given for d/DATE, the current date will be used.\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_TITLE + "Bubble Tea "
+ + PREFIX_AMOUNT + "5 "
+ + PREFIX_DATE + "03/10/2020 "
+ + PREFIX_CATEGORY + "Food & Beverage";
+
+ public static final String MESSAGE_SUCCESS = "New expense added: %1$s";
+
+ private final Expense toAdd;
+
+ /**
+ * Creates an AddExpenseCommand to add the specified {@code Expense}.
+ */
+ public AddExpenseCommand(Expense expense) {
+ requireNonNull(expense);
+ toAdd = expense;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+
+ model.addExpense(toAdd);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd), true, Tab.EXPENSES);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AddExpenseCommand // instanceof handles nulls
+ && toAdd.equals(((AddExpenseCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/AddIncomeCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/AddIncomeCommand.java
new file mode 100644
index 00000000000..966ddb0388c
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/AddIncomeCommand.java
@@ -0,0 +1,60 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_CATEGORY;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_DATE;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_TITLE;
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Income;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState.Tab;
+
+/**
+ * Adds an income to the finance tracker.
+ */
+public class AddIncomeCommand extends Command {
+
+ public static final String COMMAND_WORD = "add-income";
+ public static final String COMMAND_ALIAS = "addi";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an income to the finance tracker. "
+ + "Parameters: "
+ + PREFIX_TITLE + "TITLE "
+ + PREFIX_AMOUNT + "AMOUNT "
+ + "[" + PREFIX_DATE + "DATE] "
+ + "[" + PREFIX_CATEGORY + "CATEGORY]...\n"
+ + "Inputting the date is optional. If no input is given for d/DATE, the current date will be used.\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_TITLE + "Internship "
+ + PREFIX_AMOUNT + "560 "
+ + PREFIX_DATE + "03/10/2020 "
+ + PREFIX_CATEGORY + "Work";
+
+ public static final String MESSAGE_SUCCESS = "New income added: %1$s";
+
+ private final Income toAdd;
+
+ /**
+ * Creates an AddIncomeCommand to add the specified {@code Income}.
+ */
+ public AddIncomeCommand(Income income) {
+ requireNonNull(income);
+ toAdd = income;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+
+ model.addIncome(toAdd);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd), true, Tab.INCOME);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AddIncomeCommand // instanceof handles nulls
+ && toAdd.equals(((AddIncomeCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ClearCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ClearCommand.java
new file mode 100644
index 00000000000..86dadfbfbc1
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ClearCommand.java
@@ -0,0 +1,41 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.FinanceTrackerParser;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.model.FinanceTracker;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState;
+
+/**
+ * Clears the finance tracker.
+ */
+public class ClearCommand extends Command {
+
+ public static final String COMMAND_WORD = "clear";
+ public static final String MESSAGE_SUCCESS = "Finance tracker has been cleared!";
+ public static final String MESSAGE_PRIMED = "Please enter 'clear' again to confirm your decision.";
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ List recentCommands = model.getCommandHistory().recentCommands(20);
+ boolean isPrimed;
+ try {
+ isPrimed = recentCommands.size() >= 2 && new FinanceTrackerParser()
+ .parseCommand(recentCommands.get(1), new UiState()) instanceof ClearCommand;
+ } catch (ParseException e) {
+ isPrimed = false;
+ }
+ if (isPrimed) {
+ model.setFinanceTracker(new FinanceTracker());
+ return new CommandResult(MESSAGE_SUCCESS, true);
+ } else {
+ // set primed
+ return new CommandResult(MESSAGE_PRIMED);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/Command.java
similarity index 73%
rename from src/main/java/seedu/address/logic/commands/Command.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/Command.java
index 64f18992160..d96d9584afb 100644
--- a/src/main/java/seedu/address/logic/commands/Command.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/Command.java
@@ -1,7 +1,7 @@
-package seedu.address.logic.commands;
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
-import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
/**
* Represents a command with hidden internal logic and the ability to be executed.
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/CommandResult.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/CommandResult.java
new file mode 100644
index 00000000000..449ecbdf4b8
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/CommandResult.java
@@ -0,0 +1,148 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState.Tab;
+
+/**
+ * Represents the result of a command execution.
+ */
+public class CommandResult {
+
+ private final String feedbackToUser;
+
+ /** Budget information should be recalculated. */
+ private final boolean calculateBudgetInfo;
+
+ /** Help information should be shown to the user. */
+ private final boolean showHelp;
+
+ /** The application should exit. */
+ private final boolean exit;
+
+ /** An optional {@code Tab} to switch to */
+ private final Optional tabToSwitchTo;
+
+ /**
+ * Constructs a {@code CommandResult} with the specified fields.
+ *
+ * @param feedbackToUser The feedback to be displayed to the user.
+ * @param calculateBudgetInfo Whether the budget information should be recalculated.
+ * @param showHelp Whether the help dialog should be shown to the user.
+ * @param exit Whether the application should exit.
+ * @param tabToSwitchTo The tab the UI should switch to.
+ */
+ public CommandResult(String feedbackToUser, boolean calculateBudgetInfo,
+ boolean showHelp, boolean exit, Tab tabToSwitchTo) {
+ this.feedbackToUser = requireNonNull(feedbackToUser);
+ this.calculateBudgetInfo = calculateBudgetInfo;
+ this.showHelp = showHelp;
+ this.exit = exit;
+ this.tabToSwitchTo = Optional.ofNullable(tabToSwitchTo);
+ }
+
+ /**
+ * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, {@code calculateBudgetInfo},
+ * {@code showHelp}, {@code exit}, and other fields set to their default value.
+ *
+ * @param feedbackToUser The feedback to be displayed to the user.
+ * @param calculateBudgetInfo Whether the budget information should be recalculated.
+ * @param showHelp Whether the help dialog should be shown to the user.
+ * @param exit Whether the application should exit.
+ */
+ public CommandResult(String feedbackToUser, boolean calculateBudgetInfo, boolean showHelp, boolean exit) {
+ this(feedbackToUser, calculateBudgetInfo, showHelp, exit, null);
+ }
+
+ /**
+ * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, {@code tabToSwitchTo},
+ * and other fields set to their default value.
+ *
+ * @param feedbackToUser The feedback to be displayed to the user.
+ * @param tabToSwitchTo The tab the UI should switch to.
+ */
+ public CommandResult(String feedbackToUser, Tab tabToSwitchTo) {
+ this(feedbackToUser, false, false, false, tabToSwitchTo);
+ }
+
+ /**
+ * Constructs a {@code CommandResult} with the specified {@code feedbackToUser},
+ * and other fields set to their default value.
+ *
+ * @param feedbackToUser The feedback to be displayed to the user.
+ */
+ public CommandResult(String feedbackToUser) {
+ this(feedbackToUser, false, false, false, null);
+ }
+
+ /**
+ * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, {@code calculateBudgetInfo},
+ * and other fields set to their default value.
+ *
+ * @param feedbackToUser The feedback to be displayed to the user.
+ * @param calculateBudgetInfo Whether the budget information should be recalculated.
+ */
+ public CommandResult(String feedbackToUser, boolean calculateBudgetInfo) {
+ this(feedbackToUser, calculateBudgetInfo, false, false, null);
+ }
+
+ /**
+ * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, {@code calculateBudgetInfo},
+ * {@code tabToSwitchTo}, and other fields set to their default value.
+ *
+ * @param feedbackToUser The feedback to be displayed to the user.
+ * @param calculateBudgetInfo Whether the budget information should be recalculated.
+ * @param tabToSwitchTo The tab the UI should switch to.
+ */
+ public CommandResult(String feedbackToUser, boolean calculateBudgetInfo, Tab tabToSwitchTo) {
+ this(feedbackToUser, calculateBudgetInfo, false, false, tabToSwitchTo);
+ }
+
+ public String getFeedbackToUser() {
+ return feedbackToUser;
+ }
+
+ public boolean isCalculateBudgetInfo() {
+ return calculateBudgetInfo;
+ }
+
+ public boolean isShowHelp() {
+ return showHelp;
+ }
+
+ public boolean isExit() {
+ return exit;
+ }
+
+ public Optional getTabToSwitchTo() {
+ return tabToSwitchTo;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof CommandResult)) {
+ return false;
+ }
+
+ CommandResult otherCommandResult = (CommandResult) other;
+ return feedbackToUser.equals(otherCommandResult.feedbackToUser)
+ && calculateBudgetInfo == otherCommandResult.calculateBudgetInfo
+ && showHelp == otherCommandResult.showHelp
+ && exit == otherCommandResult.exit
+ && tabToSwitchTo.equals(otherCommandResult.tabToSwitchTo);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(feedbackToUser, calculateBudgetInfo, showHelp, exit, tabToSwitchTo);
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/DeleteCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/DeleteCommand.java
new file mode 100644
index 00000000000..1b6f8d3af56
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/DeleteCommand.java
@@ -0,0 +1,48 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_METHOD_SHOULD_NOT_BE_CALLED;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.index.Index;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+
+/**
+ * Deletes a transaction identified using its displayed index from the finance tracker
+ * depending on the tab the user is on.
+ *
+ * Base class for DeleteExpenseCommand and DeleteIncomeCommand.
+ */
+public class DeleteCommand extends Command {
+
+ public static final String COMMAND_WORD = "delete";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes the transaction identified by the index number used in "
+ + "the displayed transaction list on the current tab.\n"
+ + "When on Income tab: Deletes from the currently displayed income list.\n"
+ + "When on Expenses tab: Deletes from the currently displayed expenses list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ private final Index targetIndex;
+
+ public DeleteCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ protected Index getTargetIndex() {
+ return targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ throw new CommandException(MESSAGE_METHOD_SHOULD_NOT_BE_CALLED);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteCommand // instanceof handles nulls
+ && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/DeleteExpenseCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/DeleteExpenseCommand.java
new file mode 100644
index 00000000000..06ec9085909
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/DeleteExpenseCommand.java
@@ -0,0 +1,43 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_EXPENSE_DISPLAYED_INDEX;
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Expense;
+
+/**
+ * Deletes an expense identified using its displayed index from the finance tracker.
+ */
+public class DeleteExpenseCommand extends DeleteCommand {
+
+ public static final String MESSAGE_DELETE_EXPENSE_SUCCESS = "Deleted Expense: %1$s";
+
+ public DeleteExpenseCommand(DeleteCommand superCommand) {
+ super(superCommand.getTargetIndex());
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredExpenseList();
+
+ if (getTargetIndex().getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_INVALID_EXPENSE_DISPLAYED_INDEX);
+ }
+
+ Expense expenseToDelete = lastShownList.get(getTargetIndex().getZeroBased());
+ model.deleteTransaction(expenseToDelete);
+ return new CommandResult(String.format(MESSAGE_DELETE_EXPENSE_SUCCESS, expenseToDelete), true);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteExpenseCommand // instanceof handles nulls
+ && getTargetIndex().equals(((DeleteExpenseCommand) other).getTargetIndex())); // state check
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/DeleteIncomeCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/DeleteIncomeCommand.java
new file mode 100644
index 00000000000..a62676174dd
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/DeleteIncomeCommand.java
@@ -0,0 +1,43 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_INCOME_DISPLAYED_INDEX;
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Income;
+
+/**
+ * Deletes an income identified using its displayed index from the finance tracker.
+ */
+public class DeleteIncomeCommand extends DeleteCommand {
+
+ public static final String MESSAGE_DELETE_INCOME_SUCCESS = "Deleted Income: %1$s";
+
+ public DeleteIncomeCommand(DeleteCommand superCommand) {
+ super(superCommand.getTargetIndex());
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredIncomeList();
+
+ if (getTargetIndex().getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_INVALID_INCOME_DISPLAYED_INDEX);
+ }
+
+ Income incomeToDelete = lastShownList.get(getTargetIndex().getZeroBased());
+ model.deleteTransaction(incomeToDelete);
+ return new CommandResult(String.format(MESSAGE_DELETE_INCOME_SUCCESS, incomeToDelete), true);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteIncomeCommand // instanceof handles nulls
+ && getTargetIndex().equals(((DeleteIncomeCommand) other).getTargetIndex())); // state check
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/EditCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/EditCommand.java
new file mode 100644
index 00000000000..61be6840c42
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/EditCommand.java
@@ -0,0 +1,199 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_METHOD_SHOULD_NOT_BE_CALLED;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_CATEGORY;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_DATE;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_TITLE;
+import static java.util.Objects.requireNonNull;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.index.Index;
+import ay2021s1_cs2103_w16_3.finesse.commons.util.CollectionUtil;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.category.Category;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Date;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Title;
+
+/**
+ * Edits the details of an existing transaction using its displayed index from the finance tracker
+ * depending on the tab the user is on.
+ *
+ * Base class for EditExpenseCommand and EditIncomeCommand.
+ */
+public class EditCommand extends Command {
+
+ public static final String COMMAND_WORD = "edit";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the transaction identified "
+ + "by the index number used in the displayed transaction list on the current tab. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "When on Income tab: Edits from the currently displayed income list.\n"
+ + "When on Expenses tab: Edits from the currently displayed expenses list.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + "[" + PREFIX_TITLE + "TITLE] "
+ + "[" + PREFIX_AMOUNT + "AMOUNT] "
+ + "[" + PREFIX_DATE + "DATE] "
+ + "[" + PREFIX_CATEGORY + "CATEGORY]...\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_AMOUNT + "5 "
+ + PREFIX_DATE + "22/09/2020";
+
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+
+ private final Index targetIndex;
+ private final EditTransactionDescriptor editTransactionDescriptor;
+
+ /**
+ * @param targetIndex Index of the transaction in the filtered transaction list to edit.
+ * @param editTransactionDescriptor Details to edit the transaction with.
+ */
+ public EditCommand(Index targetIndex, EditTransactionDescriptor editTransactionDescriptor) {
+ requireNonNull(targetIndex);
+ requireNonNull(editTransactionDescriptor);
+
+ this.targetIndex = targetIndex;
+ this.editTransactionDescriptor = new EditTransactionDescriptor(editTransactionDescriptor);
+ }
+
+ protected Index getTargetIndex() {
+ return targetIndex;
+ }
+
+ protected EditTransactionDescriptor getEditTransactionDescriptor() {
+ return editTransactionDescriptor;
+ }
+
+ protected boolean isAmountOrDateEdited() {
+ return editTransactionDescriptor.isAmountOrDateEdited();
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ throw new CommandException(MESSAGE_METHOD_SHOULD_NOT_BE_CALLED);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditCommand)) {
+ return false;
+ }
+
+ // state check
+ EditCommand e = (EditCommand) other;
+ return targetIndex.equals(e.targetIndex)
+ && editTransactionDescriptor.equals(e.editTransactionDescriptor);
+ }
+
+ /**
+ * Stores the details to edit the transaction with. Each non-empty field value will replace the
+ * corresponding field value of the transaction.
+ */
+ public static class EditTransactionDescriptor {
+ private Title title;
+ private Amount amount;
+ private Date date;
+ private Set categories;
+
+ public EditTransactionDescriptor() {}
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code categories} is used internally.
+ */
+ public EditTransactionDescriptor(EditTransactionDescriptor toCopy) {
+ setTitle(toCopy.title);
+ setAmount(toCopy.amount);
+ setDate(toCopy.date);
+ setCategories(toCopy.categories);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(title, amount, date, categories);
+ }
+
+ /**
+ * Returns true if the amount or date is edited.
+ */
+ public boolean isAmountOrDateEdited() {
+ return amount != null || date != null;
+ }
+
+ public void setTitle(Title title) {
+ this.title = title;
+ }
+
+ public Optional getTitle() {
+ return Optional.ofNullable(title);
+ }
+
+ public void setAmount(Amount amount) {
+ this.amount = amount;
+ }
+
+ public Optional getAmount() {
+ return Optional.ofNullable(amount);
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ public Optional getDate() {
+ return Optional.ofNullable(date);
+ }
+
+ /**
+ * Sets {@code categories} to this object's {@code categories}.
+ * A defensive copy of {@code categories} is used internally.
+ */
+ public void setCategories(Set categories) {
+ this.categories = (categories != null) ? new HashSet<>(categories) : null;
+ }
+
+ /**
+ * Returns an unmodifiable category set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code categories} is null.
+ */
+ public Optional> getCategories() {
+ return (categories != null) ? Optional.of(Collections.unmodifiableSet(categories)) : Optional.empty();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditTransactionDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditTransactionDescriptor e = (EditTransactionDescriptor) other;
+
+ return getTitle().equals(e.getTitle())
+ && getAmount().equals(e.getAmount())
+ && getDate().equals(e.getDate())
+ && getCategories().equals(e.getCategories());
+ }
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/EditExpenseCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/EditExpenseCommand.java
new file mode 100644
index 00000000000..0b6dd1ad0ea
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/EditExpenseCommand.java
@@ -0,0 +1,80 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_EXPENSE_DISPLAYED_INDEX;
+import static ay2021s1_cs2103_w16_3.finesse.model.Model.PREDICATE_SHOW_ALL_TRANSACTIONS;
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Set;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.category.Category;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Date;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Expense;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Title;
+
+/**
+ * Edits an expense identified using its displayed index from the finance tracker.
+ */
+public class EditExpenseCommand extends EditCommand {
+
+ public static final String MESSAGE_EDIT_EXPENSE_SUCCESS = "Edited Expense: %1$s";
+
+ public EditExpenseCommand(EditCommand superCommand) {
+ super(superCommand.getTargetIndex(), superCommand.getEditTransactionDescriptor());
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredExpenseList();
+
+ if (getTargetIndex().getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_INVALID_EXPENSE_DISPLAYED_INDEX);
+ }
+
+ Expense expenseToEdit = lastShownList.get(getTargetIndex().getZeroBased());
+ Expense editedExpense = createEditedExpense(expenseToEdit, getEditTransactionDescriptor());
+
+ model.setTransaction(expenseToEdit, editedExpense);
+ model.updateFilteredExpenseList(PREDICATE_SHOW_ALL_TRANSACTIONS);
+ return new CommandResult(String.format(MESSAGE_EDIT_EXPENSE_SUCCESS, editedExpense), isAmountOrDateEdited());
+ }
+
+ /**
+ * Creates and returns an {@code Expense} with the details of {@code expenseToEdit}
+ * edited with {@code editExpenseDescriptor}.
+ */
+ private static Expense createEditedExpense(Expense expenseToEdit,
+ EditTransactionDescriptor editExpenseDescriptor) {
+ assert expenseToEdit != null;
+
+ Title updatedTitle = editExpenseDescriptor.getTitle().orElse(expenseToEdit.getTitle());
+ Amount updatedAmount = editExpenseDescriptor.getAmount().orElse(expenseToEdit.getAmount());
+ Date updatedDate = editExpenseDescriptor.getDate().orElse(expenseToEdit.getDate());
+ Set updatedCategories = editExpenseDescriptor.getCategories()
+ .orElse(expenseToEdit.getCategories());
+
+ return new Expense(updatedTitle, updatedAmount, updatedDate, updatedCategories);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditExpenseCommand)) {
+ return false;
+ }
+
+ // state check
+ EditExpenseCommand e = (EditExpenseCommand) other;
+ return getTargetIndex().equals(e.getTargetIndex())
+ && getEditTransactionDescriptor().equals(e.getEditTransactionDescriptor());
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/EditIncomeCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/EditIncomeCommand.java
new file mode 100644
index 00000000000..6fa32643b87
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/EditIncomeCommand.java
@@ -0,0 +1,80 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_INCOME_DISPLAYED_INDEX;
+import static ay2021s1_cs2103_w16_3.finesse.model.Model.PREDICATE_SHOW_ALL_TRANSACTIONS;
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Set;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.category.Category;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Date;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Income;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Title;
+
+/**
+ * Edits an income identified using its displayed index from the finance tracker.
+ */
+public class EditIncomeCommand extends EditCommand {
+
+ public static final String MESSAGE_EDIT_INCOME_SUCCESS = "Edited Income: %1$s";
+
+ public EditIncomeCommand(EditCommand superCommand) {
+ super(superCommand.getTargetIndex(), superCommand.getEditTransactionDescriptor());
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredIncomeList();
+
+ if (getTargetIndex().getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_INVALID_INCOME_DISPLAYED_INDEX);
+ }
+
+ Income incomeToEdit = lastShownList.get(getTargetIndex().getZeroBased());
+ Income editedIncome = createEditedIncome(incomeToEdit, getEditTransactionDescriptor());
+
+ model.setTransaction(incomeToEdit, editedIncome);
+ model.updateFilteredIncomeList(PREDICATE_SHOW_ALL_TRANSACTIONS);
+ return new CommandResult(String.format(MESSAGE_EDIT_INCOME_SUCCESS, editedIncome), isAmountOrDateEdited());
+ }
+
+ /**
+ * Creates and returns an {@code Income} with the details of {@code incomeToEdit}
+ * edited with {@code editIncomeDescriptor}.
+ */
+ private static Income createEditedIncome(Income incomeToEdit,
+ EditTransactionDescriptor editIncomeDescriptor) {
+ assert incomeToEdit != null;
+
+ Title updatedTitle = editIncomeDescriptor.getTitle().orElse(incomeToEdit.getTitle());
+ Amount updatedAmount = editIncomeDescriptor.getAmount().orElse(incomeToEdit.getAmount());
+ Date updatedDate = editIncomeDescriptor.getDate().orElse(incomeToEdit.getDate());
+ Set updatedCategories = editIncomeDescriptor.getCategories()
+ .orElse(incomeToEdit.getCategories());
+
+ return new Income(updatedTitle, updatedAmount, updatedDate, updatedCategories);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditIncomeCommand)) {
+ return false;
+ }
+
+ // state check
+ EditIncomeCommand e = (EditIncomeCommand) other;
+ return getTargetIndex().equals(e.getTargetIndex())
+ && getEditTransactionDescriptor().equals(e.getEditTransactionDescriptor());
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ExitCommand.java
similarity index 68%
rename from src/main/java/seedu/address/logic/commands/ExitCommand.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ExitCommand.java
index 3dd85a8ba90..0165b37ae8e 100644
--- a/src/main/java/seedu/address/logic/commands/ExitCommand.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ExitCommand.java
@@ -1,6 +1,6 @@
-package seedu.address.logic.commands;
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
-import seedu.address.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
/**
* Terminates the program.
@@ -9,11 +9,11 @@ public class ExitCommand extends Command {
public static final String COMMAND_WORD = "exit";
- public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ...";
+ public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Fine$$e as requested ...";
@Override
public CommandResult execute(Model model) {
- return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
+ return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, false, true);
}
}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/FindCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/FindCommand.java
new file mode 100644
index 00000000000..690f5e696ad
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/FindCommand.java
@@ -0,0 +1,73 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_METHOD_SHOULD_NOT_BE_CALLED;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT_FROM;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT_TO;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_CATEGORY;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_DATE;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_DATE_FROM;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_DATE_TO;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_TITLE;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Transaction;
+
+/**
+ * Finds and lists all transactions in the finance tracker whose title contains any of the argument keywords
+ * depending on the tab the user is on.
+ * Keyword matching is case insensitive.
+ *
+ * Base class for FindExpenseCommand, FindIncomeCommand and FindTransactionCommand.
+ */
+public class FindCommand extends Command {
+
+ public static final String COMMAND_WORD = "find";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all transactions on the current tab "
+ + "that match any of the specified search parameters "
+ + "and displays them as a list with index numbers.\n"
+ + "When on Overview tab: Searches all transactions.\n"
+ + "When on Income tab: Searches all incomes.\n"
+ + "When on Expenses tab: Searches all expenses.\n"
+ + "Parameters: [" + PREFIX_TITLE + "TITLE_KEYPHRASE...] "
+ + "[" + PREFIX_AMOUNT + "AMOUNT] "
+ + "[" + PREFIX_DATE + "DATE] "
+ + "[" + PREFIX_CATEGORY + "CATEGORY...] "
+ + "[" + PREFIX_AMOUNT_FROM + "AMOUNT_FROM] "
+ + "[" + PREFIX_AMOUNT_TO + "AMOUNT_TO] "
+ + "[" + PREFIX_DATE_FROM + "DATE_FROM] "
+ + "[" + PREFIX_DATE_TO + "DATE_TO]\n"
+ + "Please refer to the user guide for more details on each search parameter.\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_TITLE + "Bubble Tea "
+ + PREFIX_AMOUNT_FROM + "5 "
+ + PREFIX_DATE_FROM + "01/10/2020 "
+ + PREFIX_DATE_TO + "31/10/2020";
+
+ private final List> predicates;
+
+ public FindCommand(List> predicates) {
+ this.predicates = predicates;
+ }
+
+ protected List> getPredicates() {
+ return predicates;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ throw new CommandException(MESSAGE_METHOD_SHOULD_NOT_BE_CALLED);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FindCommand // instanceof handles nulls
+ && predicates.equals(((FindCommand) other).predicates)); // state check
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/FindExpenseCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/FindExpenseCommand.java
new file mode 100644
index 00000000000..80a0965a126
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/FindExpenseCommand.java
@@ -0,0 +1,34 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_EXPENSES_LISTED_OVERVIEW;
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+
+/**
+ * Finds and lists all expenses in the finance tracker whose title contains any of the argument keywords.
+ * Used when the user is on the Expenses tab.
+ * Keyword matching is case insensitive.
+ */
+public class FindExpenseCommand extends FindCommand {
+
+ public FindExpenseCommand(FindCommand superCommand) {
+ super(superCommand.getPredicates());
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredExpenseList(getPredicates());
+ return new CommandResult(
+ String.format(MESSAGE_EXPENSES_LISTED_OVERVIEW,
+ model.getFilteredExpenseList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FindExpenseCommand // instanceof handles nulls
+ && getPredicates().equals(((FindExpenseCommand) other).getPredicates())); // state check
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/FindIncomeCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/FindIncomeCommand.java
new file mode 100644
index 00000000000..6e09e22bce0
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/FindIncomeCommand.java
@@ -0,0 +1,34 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INCOMES_LISTED_OVERVIEW;
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+
+/**
+ * Finds and lists all incomes in the finance tracker whose title contains any of the argument keywords.
+ * Used when the user is on the Income tab.
+ * Keyword matching is case insensitive.
+ */
+public class FindIncomeCommand extends FindCommand {
+
+ public FindIncomeCommand(FindCommand superCommand) {
+ super(superCommand.getPredicates());
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredIncomeList(getPredicates());
+ return new CommandResult(
+ String.format(MESSAGE_INCOMES_LISTED_OVERVIEW,
+ model.getFilteredIncomeList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FindIncomeCommand // instanceof handles nulls
+ && getPredicates().equals(((FindIncomeCommand) other).getPredicates())); // state check
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/FindTransactionCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/FindTransactionCommand.java
new file mode 100644
index 00000000000..a525875bd83
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/FindTransactionCommand.java
@@ -0,0 +1,34 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_TRANSACTIONS_LISTED_OVERVIEW;
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+
+/**
+ * Finds and lists all transactions in the finance tracker whose title contains any of the argument keywords.
+ * Used when the user is on the Overview tab.
+ * Keyword matching is case insensitive.
+ */
+public class FindTransactionCommand extends FindCommand {
+
+ public FindTransactionCommand(FindCommand superCommand) {
+ super(superCommand.getPredicates());
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredTransactionList(getPredicates());
+ return new CommandResult(
+ String.format(MESSAGE_TRANSACTIONS_LISTED_OVERVIEW,
+ model.getFilteredTransactionList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FindTransactionCommand // instanceof handles nulls
+ && getPredicates().equals(((FindTransactionCommand) other).getPredicates())); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/HelpCommand.java
similarity index 72%
rename from src/main/java/seedu/address/logic/commands/HelpCommand.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/HelpCommand.java
index bf824f91bd0..850128c32d5 100644
--- a/src/main/java/seedu/address/logic/commands/HelpCommand.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/HelpCommand.java
@@ -1,6 +1,6 @@
-package seedu.address.logic.commands;
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
-import seedu.address.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
/**
* Format full help instructions for every command for display.
@@ -16,6 +16,6 @@ public class HelpCommand extends Command {
@Override
public CommandResult execute(Model model) {
- return new CommandResult(SHOWING_HELP_MESSAGE, true, false);
+ return new CommandResult(SHOWING_HELP_MESSAGE, false, true, false);
}
}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ListCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ListCommand.java
new file mode 100644
index 00000000000..36d87ea274c
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ListCommand.java
@@ -0,0 +1,21 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_METHOD_SHOULD_NOT_BE_CALLED;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+
+/**
+ * Lists all transactions in the finance tracker to the user.
+ */
+public class ListCommand extends Command {
+
+ public static final String COMMAND_WORD = "list";
+
+ public static final String MESSAGE_SUCCESS = "Listed all transactions";
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ throw new CommandException(MESSAGE_METHOD_SHOULD_NOT_BE_CALLED);
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ListExpenseCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ListExpenseCommand.java
new file mode 100644
index 00000000000..5204c6eb09f
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ListExpenseCommand.java
@@ -0,0 +1,26 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static ay2021s1_cs2103_w16_3.finesse.model.Model.PREDICATE_SHOW_ALL_TRANSACTIONS;
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState.Tab;
+
+/**
+ * Lists all expenses in the finance tracker to the user.
+ */
+public class ListExpenseCommand extends Command {
+
+ public static final String COMMAND_WORD = "ls-expense";
+ public static final String COMMAND_ALIAS = "lse";
+
+ public static final String MESSAGE_SUCCESS = "Listed all expenses.";
+
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredExpenseList(PREDICATE_SHOW_ALL_TRANSACTIONS);
+ return new CommandResult(MESSAGE_SUCCESS, Tab.EXPENSES);
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ListIncomeCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ListIncomeCommand.java
new file mode 100644
index 00000000000..f3bc149bdce
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ListIncomeCommand.java
@@ -0,0 +1,26 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static ay2021s1_cs2103_w16_3.finesse.model.Model.PREDICATE_SHOW_ALL_TRANSACTIONS;
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState.Tab;
+
+/**
+ * Lists all income in the finance tracker to the user.
+ */
+public class ListIncomeCommand extends Command {
+
+ public static final String COMMAND_WORD = "ls-income";
+ public static final String COMMAND_ALIAS = "lsi";
+
+ public static final String MESSAGE_SUCCESS = "Listed all income.";
+
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredIncomeList(PREDICATE_SHOW_ALL_TRANSACTIONS);
+ return new CommandResult(MESSAGE_SUCCESS, Tab.INCOME);
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ListTransactionCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ListTransactionCommand.java
new file mode 100644
index 00000000000..f69e100a6c5
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/ListTransactionCommand.java
@@ -0,0 +1,26 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static ay2021s1_cs2103_w16_3.finesse.model.Model.PREDICATE_SHOW_ALL_TRANSACTIONS;
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState.Tab;
+
+/**
+ * Lists all transactions in the finance tracker to the user.
+ */
+public class ListTransactionCommand extends Command {
+
+ public static final String COMMAND_WORD = "ls-overview";
+ public static final String COMMAND_ALIAS = "lso";
+
+ public static final String MESSAGE_SUCCESS = "Listed all transactions.";
+
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+ model.updateFilteredTransactionList(PREDICATE_SHOW_ALL_TRANSACTIONS);
+ return new CommandResult(MESSAGE_SUCCESS, Tab.OVERVIEW);
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/TabCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/TabCommand.java
new file mode 100644
index 00000000000..4cd86dbefca
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/TabCommand.java
@@ -0,0 +1,59 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.index.Index;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState.Tab;
+
+/**
+ * Switches UI tabs.
+ */
+public class TabCommand extends Command {
+
+ /** The number of tabs in the UI. */
+ public static final int NUM_OF_TABS = 4;
+
+ public static final String COMMAND_WORD = "tab";
+
+ // TODO: Update this
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Switches to the specified tab by index.\n"
+ + "Parameters: INDEX (must be a positive integer between 1 to " + NUM_OF_TABS + " inclusive)";
+
+ public static final String MESSAGE_SWITCH_TABS_SUCCESS = "Switched to %1$s tab.";
+ public static final String MESSAGE_TAB_DOES_NOT_EXIST = "The specified tab does not exist.";
+
+ /** The index of the tab to switch to. */
+ private final Index tabIndex;
+
+ /**
+ * Constructs a {@code TabCommand} with the specified tab index to switch to.
+ *
+ * @param tabIndex The index of the tab to switch to.
+ * @throws CommandException If the specified tab index does not exist.
+ */
+ public TabCommand(Index tabIndex) {
+ requireNonNull(tabIndex);
+
+ this.tabIndex = tabIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ if (tabIndex.getOneBased() > NUM_OF_TABS) {
+ throw new CommandException(TabCommand.MESSAGE_TAB_DOES_NOT_EXIST);
+ }
+
+ Tab tabToSwitchTo = Tab.values()[tabIndex.getZeroBased()];
+ String formattedSuccessMessage = String.format(MESSAGE_SWITCH_TABS_SUCCESS, tabToSwitchTo);
+ return new CommandResult(formattedSuccessMessage, tabToSwitchTo);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // Short circuit if same object.
+ || (other instanceof TabCommand // instanceof handles nulls.
+ && tabIndex.equals(((TabCommand) other).tabIndex)); // State check.
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/AddBookmarkExpenseCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/AddBookmarkExpenseCommand.java
new file mode 100644
index 00000000000..35b923b1273
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/AddBookmarkExpenseCommand.java
@@ -0,0 +1,62 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark;
+
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_CATEGORY;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_TITLE;
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.Command;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.CommandResult;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkExpense;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.exceptions.DuplicateBookmarkTransactionException;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState.Tab;
+
+public class AddBookmarkExpenseCommand extends Command {
+
+ public static final String COMMAND_WORD = "add-bookmark-expense";
+ public static final String COMMAND_ALIAS = "addbe";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a bookmark expense to the finance tracker. "
+ + "Parameters: "
+ + PREFIX_TITLE + "TITLE "
+ + PREFIX_AMOUNT + "AMOUNT "
+ + "[" + PREFIX_CATEGORY + "CATEGORY]...\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_TITLE + "Phone Bill "
+ + PREFIX_AMOUNT + "24 "
+ + PREFIX_CATEGORY + "Utilities";
+
+ public static final String MESSAGE_SUCCESS = "New bookmark expense added: %1$s";
+
+ private final BookmarkExpense toAdd;
+
+ /**
+ * Creates an AddBookmarkExpenseCommand to add the specified {@code BookmarkExpense}
+ */
+ public AddBookmarkExpenseCommand(BookmarkExpense bookmarkExpense) {
+ requireNonNull(bookmarkExpense);
+ toAdd = bookmarkExpense;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ try {
+ model.addBookmarkExpense(toAdd);
+ } catch (DuplicateBookmarkTransactionException e) {
+ throw new CommandException(e.getMessage());
+ }
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd), Tab.EXPENSES);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AddBookmarkExpenseCommand // instanceof handles nulls
+ && toAdd.equals(((AddBookmarkExpenseCommand) other).toAdd));
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/AddBookmarkIncomeCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/AddBookmarkIncomeCommand.java
new file mode 100644
index 00000000000..4c3f3e9a715
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/AddBookmarkIncomeCommand.java
@@ -0,0 +1,63 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark;
+
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_CATEGORY;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_TITLE;
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.Command;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.CommandResult;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkIncome;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.exceptions.DuplicateBookmarkTransactionException;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState.Tab;
+
+public class AddBookmarkIncomeCommand extends Command {
+
+ public static final String COMMAND_WORD = "add-bookmark-income";
+ public static final String COMMAND_ALIAS = "addbi";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a bookmark income to the finance tracker. "
+ + "Parameters: "
+ + PREFIX_TITLE + "TITLE "
+ + PREFIX_AMOUNT + "AMOUNT "
+ + "[" + PREFIX_CATEGORY + "CATEGORY]...\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_TITLE + "Summer Internship "
+ + PREFIX_AMOUNT + "1000 "
+ + PREFIX_CATEGORY + "Work";
+
+ public static final String MESSAGE_SUCCESS = "New bookmark income added: %1$s";
+
+ private final BookmarkIncome toAdd;
+
+ /**
+ * Creates an AddBookmarkIncomeCommand to add the specified {@code BookmarkIncome}
+ * @param bookmarkIncome
+ */
+ public AddBookmarkIncomeCommand(BookmarkIncome bookmarkIncome) {
+ requireNonNull(bookmarkIncome);
+ toAdd = bookmarkIncome;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ try {
+ model.addBookmarkIncome(toAdd);
+ } catch (DuplicateBookmarkTransactionException e) {
+ throw new CommandException(e.getMessage());
+ }
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd), Tab.INCOME);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AddBookmarkIncomeCommand // instanceof handles nulls
+ && toAdd.equals(((AddBookmarkIncomeCommand) other).toAdd));
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/ConvertBookmarkCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/ConvertBookmarkCommand.java
new file mode 100644
index 00000000000..481cf43c3fd
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/ConvertBookmarkCommand.java
@@ -0,0 +1,61 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_METHOD_SHOULD_NOT_BE_CALLED;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_DATE;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.index.Index;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.Command;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.CommandResult;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Date;
+
+/**
+ * Converts a specified bookmark transaction and adds it as an expense to the finance tracker
+ * depending on the tab the user is on.
+ *
+ * Base class for ConvertBookmarkExpenseCommand and ConvertBookmarkIncomeCommand.
+ */
+public class ConvertBookmarkCommand extends Command {
+ public static final String COMMAND_WORD = "convert-bookmark";
+ public static final String COMMAND_ALIAS = "convertb";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Converts the specified bookmark transaction and adds"
+ + " it as a transaction to the finance tracker.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + "[" + PREFIX_DATE + "DATE]\n"
+ + "Inputting the date is optional. If no input is given for d/DATE, the current date will be used.\n"
+ + "Example: " + COMMAND_WORD + " 1 " + PREFIX_DATE + "03/10/2020 ";
+
+ private final Index targetIndex;
+ private final Date convertedDate;
+
+ /**
+ * @param targetIndex Index of the bookmark transaction in its respective bookmark transaction list to convert.
+ * @param convertedDate Date of conversion of bookmark transaction to a transaction.
+ */
+ public ConvertBookmarkCommand(Index targetIndex, Date convertedDate) {
+ this.targetIndex = targetIndex;
+ this.convertedDate = convertedDate;
+ }
+
+ protected Index getTargetIndex() {
+ return targetIndex;
+ }
+
+ protected Date getConvertedDate() {
+ return convertedDate;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ throw new CommandException(MESSAGE_METHOD_SHOULD_NOT_BE_CALLED);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof ConvertBookmarkCommand // instanceof handles nulls
+ && targetIndex.equals(((ConvertBookmarkCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/ConvertBookmarkExpenseCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/ConvertBookmarkExpenseCommand.java
new file mode 100644
index 00000000000..757f57eff5d
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/ConvertBookmarkExpenseCommand.java
@@ -0,0 +1,55 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_BOOKMARK_EXPENSE_DISPLAYED_INDEX;
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.CommandResult;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkExpense;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Expense;
+
+/**
+ * Converts a specified bookmark expense and adds it as an expense to the finance tracker.
+ */
+public class ConvertBookmarkExpenseCommand extends ConvertBookmarkCommand {
+
+ public static final String MESSAGE_CONVERT_BOOKMARK_EXPENSE_SUCCESS = "Bookmark expense has been converted "
+ + "and successfully added to finance tracker: %1$s";
+
+ public ConvertBookmarkExpenseCommand(ConvertBookmarkCommand superCommand) {
+ super(superCommand.getTargetIndex(), superCommand.getConvertedDate());
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredBookmarkExpenseList();
+
+ if (getTargetIndex().getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_INVALID_BOOKMARK_EXPENSE_DISPLAYED_INDEX);
+ }
+
+ BookmarkExpense bookmarkExpenseToBeConverted = lastShownList.get(getTargetIndex().getZeroBased());
+ Expense newExpenseToAdd = bookmarkExpenseToBeConverted.convert(getConvertedDate());
+ model.addExpense(newExpenseToAdd);
+ return new CommandResult(String.format(MESSAGE_CONVERT_BOOKMARK_EXPENSE_SUCCESS, newExpenseToAdd), true);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof ConvertBookmarkExpenseCommand)) {
+ return false;
+ }
+
+ ConvertBookmarkExpenseCommand otherConvertBookmarkExpenseCommand = (ConvertBookmarkExpenseCommand) other;
+ return getTargetIndex().equals(otherConvertBookmarkExpenseCommand.getTargetIndex())
+ && getConvertedDate().equals(otherConvertBookmarkExpenseCommand.getConvertedDate());
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/ConvertBookmarkIncomeCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/ConvertBookmarkIncomeCommand.java
new file mode 100644
index 00000000000..1a49056d3cb
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/ConvertBookmarkIncomeCommand.java
@@ -0,0 +1,56 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_BOOKMARK_INCOME_DISPLAYED_INDEX;
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.CommandResult;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkIncome;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Income;
+
+/**
+ * Converts a specified bookmark income and adds it as an income to the finance tracker.
+ */
+public class ConvertBookmarkIncomeCommand extends ConvertBookmarkCommand {
+
+ public static final String MESSAGE_CONVERT_BOOKMARK_INCOME_SUCCESS = "Bookmark income has been converted "
+ + "and successfully added to finance tracker: %1$s";
+
+ public ConvertBookmarkIncomeCommand(ConvertBookmarkCommand superCommand) {
+ super(superCommand.getTargetIndex(), superCommand.getConvertedDate());
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredBookmarkIncomeList();
+
+ if (getTargetIndex().getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_INVALID_BOOKMARK_INCOME_DISPLAYED_INDEX);
+ }
+
+ BookmarkIncome bookmarkIncomeToBeConverted = lastShownList.get(getTargetIndex().getZeroBased());
+ Income newIncomeToAdd = bookmarkIncomeToBeConverted.convert(getConvertedDate());
+ model.addIncome(newIncomeToAdd);
+ return new CommandResult(String.format(MESSAGE_CONVERT_BOOKMARK_INCOME_SUCCESS, newIncomeToAdd), true);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof ConvertBookmarkIncomeCommand)) {
+ return false;
+ }
+
+ ConvertBookmarkIncomeCommand otherConvertBookmarkIncomeCommand = (ConvertBookmarkIncomeCommand) other;
+ return getTargetIndex().equals(otherConvertBookmarkIncomeCommand.getTargetIndex())
+ && getConvertedDate().equals(otherConvertBookmarkIncomeCommand.getConvertedDate());
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/DeleteBookmarkCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/DeleteBookmarkCommand.java
new file mode 100644
index 00000000000..ae84f52cfa6
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/DeleteBookmarkCommand.java
@@ -0,0 +1,49 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_METHOD_SHOULD_NOT_BE_CALLED;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.index.Index;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.Command;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.CommandResult;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+
+/**
+ * Deletes a bookmark transaction identified using its displayed index from the finance tracker
+ * depending on the tab the user is on.
+ *
+ * Base class for DeleteBookmarkExpenseCommand and DeleteBookmarkIncomeCommand.
+ */
+public class DeleteBookmarkCommand extends Command {
+ public static final String COMMAND_WORD = "delete-bookmark";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes the bookmark transaction identified by the index number used in the displayed "
+ + "bookmark transaction list on the current tab. \n"
+ + "When on Income tab: Deletes from the currently displayed bookmark income list.\n"
+ + "When on Expenses tab: Deletes from the currently displayed bookmark expenses list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1 ";
+
+ private final Index targetIndex;
+
+ public DeleteBookmarkCommand(Index index) {
+ this.targetIndex = index;
+ }
+
+ protected Index getTargetIndex() {
+ return targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ throw new CommandException(MESSAGE_METHOD_SHOULD_NOT_BE_CALLED);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteBookmarkCommand // instanceof handles nulls
+ && targetIndex.equals(((DeleteBookmarkCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/DeleteBookmarkExpenseCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/DeleteBookmarkExpenseCommand.java
new file mode 100644
index 00000000000..31be51d3884
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/DeleteBookmarkExpenseCommand.java
@@ -0,0 +1,44 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_BOOKMARK_EXPENSE_DISPLAYED_INDEX;
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.CommandResult;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkExpense;
+
+/**
+ * Deletes a bookmark expense identified using its displayed index from the finance tracker.
+ */
+public class DeleteBookmarkExpenseCommand extends DeleteBookmarkCommand {
+
+ public static final String MESSAGE_DELETE_BOOKMARK_EXPENSE_SUCCESS = "Deleted Bookmark Expense: %1$s";
+
+ public DeleteBookmarkExpenseCommand(DeleteBookmarkCommand superCommand) {
+ super(superCommand.getTargetIndex());
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredBookmarkExpenseList();
+
+ if (getTargetIndex().getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_INVALID_BOOKMARK_EXPENSE_DISPLAYED_INDEX);
+ }
+
+ BookmarkExpense bookmarkExpenseToDelete = lastShownList.get(getTargetIndex().getZeroBased());
+ model.deleteBookmarkExpense(bookmarkExpenseToDelete);
+ return new CommandResult(String.format(MESSAGE_DELETE_BOOKMARK_EXPENSE_SUCCESS, bookmarkExpenseToDelete));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteBookmarkExpenseCommand // instanceof handles nulls
+ && getTargetIndex().equals(((DeleteBookmarkExpenseCommand) other).getTargetIndex())); // state check
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/DeleteBookmarkIncomeCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/DeleteBookmarkIncomeCommand.java
new file mode 100644
index 00000000000..e6e9677d9c8
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/DeleteBookmarkIncomeCommand.java
@@ -0,0 +1,44 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_BOOKMARK_INCOME_DISPLAYED_INDEX;
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.CommandResult;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkIncome;
+
+/**
+ * Deletes a bookmark income identified using its displayed index from the finance tracker.
+ */
+public class DeleteBookmarkIncomeCommand extends DeleteBookmarkCommand {
+
+ public static final String MESSAGE_DELETE_BOOKMARK_INCOME_SUCCESS = "Deleted Bookmark Income: %1$s";
+
+ public DeleteBookmarkIncomeCommand(DeleteBookmarkCommand superCommand) {
+ super(superCommand.getTargetIndex());
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredBookmarkIncomeList();
+
+ if (getTargetIndex().getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_INVALID_BOOKMARK_INCOME_DISPLAYED_INDEX);
+ }
+
+ BookmarkIncome bookmarkIncomeToDelete = lastShownList.get(getTargetIndex().getZeroBased());
+ model.deleteBookmarkIncome(bookmarkIncomeToDelete);
+ return new CommandResult(String.format(MESSAGE_DELETE_BOOKMARK_INCOME_SUCCESS, bookmarkIncomeToDelete));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteBookmarkIncomeCommand // instanceof handles nulls
+ && getTargetIndex().equals(((DeleteBookmarkIncomeCommand) other).getTargetIndex())); // state check
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/EditBookmarkCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/EditBookmarkCommand.java
new file mode 100644
index 00000000000..4cc8953c4e4
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/EditBookmarkCommand.java
@@ -0,0 +1,84 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_METHOD_SHOULD_NOT_BE_CALLED;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_CATEGORY;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_TITLE;
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.index.Index;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.Command;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.CommandResult;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+
+/**
+ * Edits the details of an existing bookmark transaction using its displayed index from the finance tracker
+ * depending on the tab the user is on.
+ *
+ * Base class for EditBookmarkExpenseCommand and EditBookmarkIncomeCommand.
+ */
+public class EditBookmarkCommand extends Command {
+ public static final String COMMAND_WORD = "edit-bookmark";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the bookmark transaction "
+ + "identified by the index number used in the displayed bookmark transaction list on the current tab. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "When on Income tab: Edits from the currently displayed bookmark incomes list.\n"
+ + "When on Expenses tab: Edits from the currently displayed bookmark expenses list.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + "[" + PREFIX_TITLE + "TITLE] "
+ + "[" + PREFIX_AMOUNT + "AMOUNT] "
+ + "[" + PREFIX_CATEGORY + "CATEGORY]...\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_AMOUNT + "5 ";
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+
+ private final Index targetIndex;
+ private final EditBookmarkTransactionDescriptor editBookmarkTransactionDescriptor;
+
+ /**
+ * @param targetIndex Index of the bookmark transaction in the respective filtered bookmark transaction list to
+ * edit.
+ * @param editBookmarkTransactionDescriptor Details to edit the bookmark transaction with.
+ */
+ public EditBookmarkCommand(Index targetIndex, EditBookmarkTransactionDescriptor editBookmarkTransactionDescriptor) {
+ requireNonNull(targetIndex);
+ requireNonNull(editBookmarkTransactionDescriptor);
+
+ this.targetIndex = targetIndex;
+ this.editBookmarkTransactionDescriptor = editBookmarkTransactionDescriptor;
+ }
+
+ protected Index getTargetIndex() {
+ return targetIndex;
+ }
+
+ protected EditBookmarkTransactionDescriptor getEditBookmarkTransactionDescriptor() {
+ return editBookmarkTransactionDescriptor;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ throw new CommandException(MESSAGE_METHOD_SHOULD_NOT_BE_CALLED);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditBookmarkCommand)) {
+ return false;
+ }
+
+ // state check
+ EditBookmarkCommand e = (EditBookmarkCommand) other;
+ return targetIndex.equals(e.targetIndex)
+ && getEditBookmarkTransactionDescriptor().equals(e.getEditBookmarkTransactionDescriptor());
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/EditBookmarkExpenseCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/EditBookmarkExpenseCommand.java
new file mode 100644
index 00000000000..56e29372154
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/EditBookmarkExpenseCommand.java
@@ -0,0 +1,88 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_BOOKMARK_EXPENSE_DISPLAYED_INDEX;
+import static ay2021s1_cs2103_w16_3.finesse.model.Model.PREDICATE_SHOW_ALL_BOOKMARK_EXPENSES;
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Set;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.CommandResult;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkExpense;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.exceptions.DuplicateBookmarkTransactionException;
+import ay2021s1_cs2103_w16_3.finesse.model.category.Category;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Title;
+
+/**
+ * Edits the details of an existing bookmark expense using its displayed index from the bookmark expense list
+ * in the expense tab.
+ */
+public class EditBookmarkExpenseCommand extends EditBookmarkCommand {
+
+ public static final String MESSAGE_EDIT_BOOKMARK_EXPENSE_SUCCESS = "Edited Bookmark Expense: %1$s";
+
+ public EditBookmarkExpenseCommand(EditBookmarkCommand superCommand) {
+ super(superCommand.getTargetIndex(), superCommand.getEditBookmarkTransactionDescriptor());
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredBookmarkExpenseList();
+
+ if (getTargetIndex().getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_INVALID_BOOKMARK_EXPENSE_DISPLAYED_INDEX);
+ }
+
+ BookmarkExpense bookmarkExpenseToEdit = lastShownList.get(getTargetIndex().getZeroBased());
+ BookmarkExpense editedBookmarkExpense = createEditedBookmarkExpense(bookmarkExpenseToEdit,
+ getEditBookmarkTransactionDescriptor());
+ try {
+ model.setBookmarkExpense(bookmarkExpenseToEdit, editedBookmarkExpense);
+ } catch (DuplicateBookmarkTransactionException e) {
+ throw new CommandException(e.getMessage());
+ }
+ model.updateFilteredBookmarkExpenseList(PREDICATE_SHOW_ALL_BOOKMARK_EXPENSES);
+ return new CommandResult(String.format(MESSAGE_EDIT_BOOKMARK_EXPENSE_SUCCESS, editedBookmarkExpense));
+ }
+
+ /**
+ * Creates and returns a {@code BookmarkExpense} with the details of {@code bookmarkExpenseToEdit}
+ * edited with {@code editBookmarkExpenseDescriptor}.
+ */
+ private static BookmarkExpense createEditedBookmarkExpense(BookmarkExpense bookmarkExpenseToEdit,
+ EditBookmarkTransactionDescriptor
+ editBookmarkExpenseDescriptor) {
+ assert bookmarkExpenseToEdit != null;
+
+ Title updatedTitle = editBookmarkExpenseDescriptor.getTitle().orElse(bookmarkExpenseToEdit.getTitle());
+ Amount updatedAmount = editBookmarkExpenseDescriptor.getAmount().orElse(bookmarkExpenseToEdit.getAmount());
+ Set updatedCategories = editBookmarkExpenseDescriptor.getCategories()
+ .orElse(bookmarkExpenseToEdit.getCategories());
+
+ return new BookmarkExpense(updatedTitle, updatedAmount, updatedCategories);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditBookmarkExpenseCommand)) {
+ return false;
+ }
+
+ // state check
+ EditBookmarkExpenseCommand otherEditBookmarkExpenseCommand = (EditBookmarkExpenseCommand) other;
+ return getTargetIndex().equals(otherEditBookmarkExpenseCommand.getTargetIndex())
+ && getEditBookmarkTransactionDescriptor()
+ .equals(otherEditBookmarkExpenseCommand.getEditBookmarkTransactionDescriptor());
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/EditBookmarkIncomeCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/EditBookmarkIncomeCommand.java
new file mode 100644
index 00000000000..0d0f96047e7
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/EditBookmarkIncomeCommand.java
@@ -0,0 +1,88 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_BOOKMARK_INCOME_DISPLAYED_INDEX;
+import static ay2021s1_cs2103_w16_3.finesse.model.Model.PREDICATE_SHOW_ALL_BOOKMARK_INCOMES;
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.Set;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.CommandResult;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions.CommandException;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkIncome;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.exceptions.DuplicateBookmarkTransactionException;
+import ay2021s1_cs2103_w16_3.finesse.model.category.Category;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Title;
+
+/**
+ * Edits the details of an existing bookmark income using its displayed index from the bookmark income list
+ * in the income tab.
+ */
+public class EditBookmarkIncomeCommand extends EditBookmarkCommand {
+
+ public static final String MESSAGE_EDIT_BOOKMARK_INCOME_SUCCESS = "Edited Bookmark Income: %1$s";
+
+ public EditBookmarkIncomeCommand(EditBookmarkCommand superCommand) {
+ super(superCommand.getTargetIndex(), superCommand.getEditBookmarkTransactionDescriptor());
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredBookmarkIncomeList();
+
+ if (getTargetIndex().getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_INVALID_BOOKMARK_INCOME_DISPLAYED_INDEX);
+ }
+
+ BookmarkIncome bookmarkIncomeToEdit = lastShownList.get(getTargetIndex().getZeroBased());
+ BookmarkIncome editedBookmarkIncome = createdEditedBookmarkIncome(bookmarkIncomeToEdit,
+ getEditBookmarkTransactionDescriptor());
+ try {
+ model.setBookmarkIncome(bookmarkIncomeToEdit, editedBookmarkIncome);
+ } catch (DuplicateBookmarkTransactionException e) {
+ throw new CommandException(e.getMessage());
+ }
+ model.updateFilteredBookmarkIncomeList(PREDICATE_SHOW_ALL_BOOKMARK_INCOMES);
+ return new CommandResult(String.format(MESSAGE_EDIT_BOOKMARK_INCOME_SUCCESS, editedBookmarkIncome));
+ }
+
+ /**
+ * Creates and returns a {@code BookmarkIncome} with the details of {@code bookmarkIncomeToEdit}
+ * edited with {@code editBookmarkIncomeDescriptor}.
+ */
+ private static BookmarkIncome createdEditedBookmarkIncome(BookmarkIncome bookmarkIncomeToEdit,
+ EditBookmarkTransactionDescriptor
+ editBookmarkIncomeDescriptor) {
+ assert bookmarkIncomeToEdit != null;
+
+ Title updatedTitle = editBookmarkIncomeDescriptor.getTitle().orElse(bookmarkIncomeToEdit.getTitle());
+ Amount updatedAmount = editBookmarkIncomeDescriptor.getAmount().orElse(bookmarkIncomeToEdit.getAmount());
+ Set updatedCategories = editBookmarkIncomeDescriptor.getCategories()
+ .orElse(bookmarkIncomeToEdit.getCategories());
+
+ return new BookmarkIncome(updatedTitle, updatedAmount, updatedCategories);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditBookmarkIncomeCommand)) {
+ return false;
+ }
+
+ // state check
+ EditBookmarkIncomeCommand otherEditBookmarkIncomeCommand = (EditBookmarkIncomeCommand) other;
+ return getTargetIndex().equals(otherEditBookmarkIncomeCommand.getTargetIndex())
+ && getEditBookmarkTransactionDescriptor()
+ .equals(otherEditBookmarkIncomeCommand.getEditBookmarkTransactionDescriptor());
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/EditBookmarkTransactionDescriptor.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/EditBookmarkTransactionDescriptor.java
new file mode 100644
index 00000000000..6da260458f4
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/bookmark/EditBookmarkTransactionDescriptor.java
@@ -0,0 +1,94 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.util.CollectionUtil;
+import ay2021s1_cs2103_w16_3.finesse.model.category.Category;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Title;
+
+/**
+ * Stores the details to edit the bookmark transaction with. Each non-empty field value will replace the
+ * corresponding field value of the bookmark transaction.
+ */
+public class EditBookmarkTransactionDescriptor {
+ private Title title;
+ private Amount amount;
+ private Set categories;
+
+ public EditBookmarkTransactionDescriptor() { }
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code categories} is used internally.
+ */
+ public EditBookmarkTransactionDescriptor(EditBookmarkTransactionDescriptor toCopy) {
+ setTitle(toCopy.title);
+ setAmount(toCopy.amount);
+ setCategories(toCopy.categories);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(title, amount, categories);
+ }
+
+ public void setTitle(Title title) {
+ this.title = title;
+ }
+
+ public Optional getTitle() {
+ return Optional.ofNullable(title);
+ }
+
+ public void setAmount(Amount amount) {
+ this.amount = amount;
+ }
+
+ public Optional getAmount() {
+ return Optional.ofNullable(amount);
+ }
+
+ /**
+ * Sets {@code categories} to this object's {@code categories}.
+ * A defensive copy of {@code categories} is used internally.
+ */
+ public void setCategories(Set categories) {
+ this.categories = (categories != null) ? new HashSet<>(categories) : null;
+ }
+
+ /**
+ * Returns an unmodifiable category set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code categories} is null.
+ */
+ public Optional> getCategories() {
+ return (categories != null) ? Optional.of(Collections.unmodifiableSet(categories)) : Optional.empty();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditBookmarkTransactionDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditBookmarkTransactionDescriptor e = (EditBookmarkTransactionDescriptor) other;
+
+ return getTitle().equals(e.getTitle())
+ && getAmount().equals(e.getAmount())
+ && getCategories().equals(e.getCategories());
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/budget/SetExpenseLimitCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/budget/SetExpenseLimitCommand.java
new file mode 100644
index 00000000000..24b72c20d99
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/budget/SetExpenseLimitCommand.java
@@ -0,0 +1,50 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands.budget;
+
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT;
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.Command;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.CommandResult;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState.Tab;
+
+/**
+ * Sets the monthly expense limit in the finance tracker.
+ */
+public class SetExpenseLimitCommand extends Command {
+
+ public static final String COMMAND_WORD = "set-expense-limit";
+ public static final String COMMAND_ALIAS = "setel";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sets the monthly expense limit. "
+ + "Parameters: " + PREFIX_AMOUNT + "AMOUNT\n"
+ + "Example: " + COMMAND_WORD + " " + PREFIX_AMOUNT + "500";
+
+ public static final String MESSAGE_SUCCESS = "New monthly expense limit set: %1$s";
+
+ private final Amount amount;
+
+ /**
+ * Creates a SetExpenseLimitCommand to set the monthly expense limit.
+ */
+ public SetExpenseLimitCommand(Amount amount) {
+ requireNonNull(amount);
+ this.amount = amount;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+
+ model.setExpenseLimit(amount);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, amount), true, Tab.OVERVIEW);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof SetExpenseLimitCommand // instanceof handles nulls
+ && amount.equals(((SetExpenseLimitCommand) other).amount));
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/budget/SetSavingsGoalCommand.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/budget/SetSavingsGoalCommand.java
new file mode 100644
index 00000000000..eca5e9628d0
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/budget/SetSavingsGoalCommand.java
@@ -0,0 +1,50 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.commands.budget;
+
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT;
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.Command;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.CommandResult;
+import ay2021s1_cs2103_w16_3.finesse.model.Model;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState.Tab;
+
+/**
+ * Sets the monthly savings goal in the finance tracker.
+ */
+public class SetSavingsGoalCommand extends Command {
+
+ public static final String COMMAND_WORD = "set-savings-goal";
+ public static final String COMMAND_ALIAS = "setsg";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Sets the monthly savings goal. "
+ + "Parameters: " + PREFIX_AMOUNT + "AMOUNT\n"
+ + "Example: " + COMMAND_WORD + " " + PREFIX_AMOUNT + "500";
+
+ public static final String MESSAGE_SUCCESS = "New monthly savings goal set: %1$s";
+
+ private final Amount amount;
+
+ /**
+ * Creates a SetSavingsGoalCommand to set the monthly savings goal.
+ */
+ public SetSavingsGoalCommand(Amount amount) {
+ requireNonNull(amount);
+ this.amount = amount;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+
+ model.setSavingsGoal(amount);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, amount), true, Tab.OVERVIEW);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof SetSavingsGoalCommand // instanceof handles nulls
+ && amount.equals(((SetSavingsGoalCommand) other).amount));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/exceptions/CommandException.java
similarity index 87%
rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/exceptions/CommandException.java
index a16bd14f2cd..3752a712fbf 100644
--- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/commands/exceptions/CommandException.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.commands.exceptions;
+package ay2021s1_cs2103_w16_3.finesse.logic.commands.exceptions;
/**
* Represents an error which occurs during execution of a {@link Command}.
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/AddExpenseCommandParser.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/AddExpenseCommandParser.java
new file mode 100644
index 00000000000..5140c3d7864
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/AddExpenseCommandParser.java
@@ -0,0 +1,24 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.AddExpenseCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Expense;
+
+/**
+ * Parses input arguments and creates a new AddExpenseCommand object
+ */
+public class AddExpenseCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddExpenseCommand
+ * and returns an AddExpenseCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AddExpenseCommand parse(String args) throws ParseException {
+ TransactionBuilder transactionBuilder = ParserUtil.parseTransactionBuilder(args,
+ AddExpenseCommand.MESSAGE_USAGE);
+ Expense expense = transactionBuilder.buildExpense();
+ return new AddExpenseCommand(expense);
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/AddIncomeCommandParser.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/AddIncomeCommandParser.java
new file mode 100644
index 00000000000..1240c61a232
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/AddIncomeCommandParser.java
@@ -0,0 +1,24 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.AddIncomeCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Income;
+
+/**
+ * Parses input arguments and creates a new AddIncomeCommand object
+ */
+public class AddIncomeCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddIncomeCommand
+ * and returns an AddIncomeCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AddIncomeCommand parse(String args) throws ParseException {
+ TransactionBuilder transactionBuilder = ParserUtil.parseTransactionBuilder(args,
+ AddIncomeCommand.MESSAGE_USAGE);
+ Income income = transactionBuilder.buildIncome();
+ return new AddIncomeCommand(income);
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/ArgumentMultimap.java
similarity index 69%
rename from src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/ArgumentMultimap.java
index 954c8e18f8e..fad993b1371 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/ArgumentMultimap.java
@@ -1,10 +1,11 @@
-package seedu.address.logic.parser;
+package ay2021s1_cs2103_w16_3.finesse.logic.parser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.stream.Stream;
/**
* Stores mapping of prefixes to their respective arguments.
@@ -51,6 +52,33 @@ public List getAllValues(Prefix prefix) {
return new ArrayList<>(argMultimap.get(prefix));
}
+ /**
+ * Returns true if none of the given prefixes contains empty {@code Optional} values.
+ */
+ public boolean arePrefixesPresent(Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> getValue(prefix).isPresent());
+ }
+
+ /**
+ * Returns a list of prefixes that are present in this {@code ArgumentMultimap}.
+ */
+ public List getPresentPrefixes(Prefix... prefixes) {
+ List presentPrefixes = new ArrayList<>();
+ Stream.of(prefixes).forEach(prefix -> {
+ if (getValue(prefix).isPresent()) {
+ presentPrefixes.add(prefix);
+ }
+ });
+ return presentPrefixes;
+ }
+
+ /**
+ * Returns true if the prefix contains more than one value.
+ */
+ public boolean moreThanOneValuePresent(Prefix prefix) {
+ return getAllValues(prefix).size() > 1;
+ }
+
/**
* Returns the preamble (text before the first valid prefix). Trims any leading/trailing spaces.
*/
diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/ArgumentTokenizer.java
similarity index 69%
rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/ArgumentTokenizer.java
index 5c9aebfa488..9dd2a2f207a 100644
--- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/ArgumentTokenizer.java
@@ -1,10 +1,15 @@
-package seedu.address.logic.parser;
+package ay2021s1_cs2103_w16_3.finesse.logic.parser;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_PREFIX_PRESENT_HEADER;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+
/**
* Tokenizes arguments string of the form: {@code preamble value value ...}
* e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.
@@ -15,25 +20,49 @@
*/
public class ArgumentTokenizer {
+ /**
+ * Prevents instantiation of this class.
+ */
+ private ArgumentTokenizer() {}
+
/**
* Tokenizes an arguments string and returns an {@code ArgumentMultimap} object that maps prefixes to their
- * respective argument values. Only the given prefixes will be recognized in the arguments string.
+ * respective argument values. All prefixes from {@code CliSyntax::getAllPrefixes} are recognized, but only the
+ * specified {@param prefixes} are considered valid. The other prefixes from {@param prefixFullSet} that are not
+ * specified are considered invalid and, if detected, will throw {@code ParseException}.
*
- * @param argsString Arguments string of the form: {@code preamble value value ...}
- * @param prefixes Prefixes to tokenize the arguments string with
- * @return ArgumentMultimap object that maps prefixes to their arguments
+ * @param argsString Arguments string of the form: {@code preamble value value ...}.
+ * @param prefixes Subset of prefixFullSet that are considered valid. Any other prefixes is invalid and
+ * thus should throw ParseException.
+ * @return ArgumentMultimap object that maps prefixes to their arguments.
+ * @throws ParseException if any invalid prefixes are detected.
*/
- public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) {
- List positions = findAllPrefixPositions(argsString, prefixes);
- return extractArguments(argsString, positions);
+ public static ArgumentMultimap tokenize(String argsString, String exceptionMessage, Prefix... prefixes)
+ throws ParseException {
+ List positions = findAllPrefixPositions(argsString, CliSyntax.getAllPrefixes());
+ ArgumentMultimap argumentMultimap = extractArguments(argsString, positions);
+
+ List prefixComplementList = Arrays.stream(CliSyntax.getAllPrefixes()).collect(Collectors.toList());
+ List prefixSubList = Arrays.stream(prefixes).collect(Collectors.toList());
+ prefixComplementList.removeAll(prefixSubList);
+ List presentInvalidPrefixes = argumentMultimap.getPresentPrefixes(
+ prefixComplementList.toArray(new Prefix[0]));
+
+ if (!presentInvalidPrefixes.isEmpty()) {
+ StringBuilder invalidPrefixesMessage = new StringBuilder(MESSAGE_INVALID_PREFIX_PRESENT_HEADER);
+ presentInvalidPrefixes.forEach(prefix -> invalidPrefixesMessage.append(" ").append(prefix.toString()));
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ invalidPrefixesMessage.toString() + "\n" + exceptionMessage));
+ }
+ return argumentMultimap;
}
/**
* Finds all zero-based prefix positions in the given arguments string.
*
- * @param argsString Arguments string of the form: {@code preamble value value ...}
- * @param prefixes Prefixes to find in the arguments string
- * @return List of zero-based prefix positions in the given arguments string
+ * @param argsString Arguments string of the form: {@code preamble value value ...}.
+ * @param prefixes Prefixes to find in the arguments string.
+ * @return List of zero-based prefix positions in the given arguments string.
*/
private static List findAllPrefixPositions(String argsString, Prefix... prefixes) {
return Arrays.stream(prefixes)
@@ -80,9 +109,9 @@ private static int findPrefixPosition(String argsString, String prefix, int from
* extracted prefixes to their respective arguments. Prefixes are extracted based on their zero-based positions in
* {@code argsString}.
*
- * @param argsString Arguments string of the form: {@code preamble value value ...}
- * @param prefixPositions Zero-based positions of all prefixes in {@code argsString}
- * @return ArgumentMultimap object that maps prefixes to their arguments
+ * @param argsString Arguments string of the form: {@code preamble value value ...}.
+ * @param prefixPositions Zero-based positions of all prefixes in {@code argsString}.
+ * @return ArgumentMultimap object that maps prefixes to their arguments.
*/
private static ArgumentMultimap extractArguments(String argsString, List prefixPositions) {
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/CliSyntax.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/CliSyntax.java
new file mode 100644
index 00000000000..6feaf6ac322
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/CliSyntax.java
@@ -0,0 +1,33 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser;
+
+/**
+ * Contains Command Line Interface (CLI) syntax definitions common to multiple commands
+ */
+public class CliSyntax {
+
+ /* Prefix definitions */
+ public static final Prefix PREFIX_TITLE = new Prefix("t/");
+ public static final Prefix PREFIX_AMOUNT = new Prefix("a/");
+ public static final Prefix PREFIX_DATE = new Prefix("d/");
+ public static final Prefix PREFIX_CATEGORY = new Prefix("c/");
+ public static final Prefix PREFIX_AMOUNT_FROM = new Prefix("af/");
+ public static final Prefix PREFIX_AMOUNT_TO = new Prefix("at/");
+ public static final Prefix PREFIX_DATE_FROM = new Prefix("df/");
+ public static final Prefix PREFIX_DATE_TO = new Prefix("dt/");
+
+ /**
+ * Prevents instantiation of this class.
+ */
+ private CliSyntax() {}
+
+ /**
+ * Retrieves a list containing all prefixes.
+ *
+ * @return List of all prefixes.
+ */
+ public static Prefix[] getAllPrefixes() {
+ return new Prefix[] {PREFIX_TITLE, PREFIX_AMOUNT, PREFIX_DATE, PREFIX_CATEGORY, PREFIX_AMOUNT_FROM,
+ PREFIX_AMOUNT_TO, PREFIX_DATE_FROM, PREFIX_DATE_TO};
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/DeleteCommandParser.java
similarity index 65%
rename from src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/DeleteCommandParser.java
index 522b93081cc..79b113c5759 100644
--- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/DeleteCommandParser.java
@@ -1,10 +1,10 @@
-package seedu.address.logic.parser;
+package ay2021s1_cs2103_w16_3.finesse.logic.parser;
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.DeleteCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.commons.core.index.Index;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.DeleteCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
/**
* Parses input arguments and creates a new DeleteCommand object
@@ -19,6 +19,7 @@ public class DeleteCommandParser implements Parser {
public DeleteCommand parse(String args) throws ParseException {
try {
Index index = ParserUtil.parseIndex(args);
+ assert index.getZeroBased() >= 0;
return new DeleteCommand(index);
} catch (ParseException pe) {
throw new ParseException(
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/EditCommandParser.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/EditCommandParser.java
new file mode 100644
index 00000000000..8652cff5ac0
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/EditCommandParser.java
@@ -0,0 +1,74 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_CATEGORY;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_DATE;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_TITLE;
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.index.Index;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.EditCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.EditCommand.EditTransactionDescriptor;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Transaction;
+
+/**
+ * Parses input arguments and creates a new EditCommand object
+ */
+public class EditCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EditCommand
+ * and returns an EditCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EditCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, EditCommand.MESSAGE_USAGE, PREFIX_TITLE,
+ PREFIX_AMOUNT, PREFIX_DATE, PREFIX_CATEGORY);;
+
+ Index index;
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_TITLE)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, Transaction.MESSAGE_TITLE_CONSTRAINTS));
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_AMOUNT)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, Transaction.MESSAGE_AMOUNT_CONSTRAINTS));
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_DATE)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, Transaction.MESSAGE_DATE_CONSTRAINTS));
+ }
+
+ EditTransactionDescriptor editTransactionDescriptor = new EditTransactionDescriptor();
+ if (argMultimap.getValue(PREFIX_TITLE).isPresent()) {
+ editTransactionDescriptor.setTitle(ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE).get()));
+ }
+ if (argMultimap.getValue(PREFIX_AMOUNT).isPresent()) {
+ editTransactionDescriptor.setAmount(ParserUtil.parseAmount(argMultimap.getValue(PREFIX_AMOUNT).get()));
+ }
+ if (argMultimap.getValue(PREFIX_DATE).isPresent()) {
+ editTransactionDescriptor.setDate(ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE).get()));
+ }
+ ParserUtil.parseCategoriesForEdit(argMultimap.getAllValues(PREFIX_CATEGORY))
+ .ifPresent(editTransactionDescriptor::setCategories);
+
+ if (!editTransactionDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EditCommand(index, editTransactionDescriptor);
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/FinanceTrackerParser.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/FinanceTrackerParser.java
new file mode 100644
index 00000000000..26e58481733
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/FinanceTrackerParser.java
@@ -0,0 +1,282 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_TAB_FORMAT;
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_NO_ARGUMENTS_COMMAND_FORMAT;
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
+import static java.util.Objects.requireNonNull;
+
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.LogsCenter;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.AddExpenseCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.AddIncomeCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.ClearCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.Command;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.DeleteCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.DeleteExpenseCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.DeleteIncomeCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.EditCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.EditExpenseCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.EditIncomeCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.ExitCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.FindCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.FindExpenseCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.FindIncomeCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.FindTransactionCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.HelpCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.ListCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.ListExpenseCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.ListIncomeCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.ListTransactionCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.TabCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.AddBookmarkExpenseCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.AddBookmarkIncomeCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.ConvertBookmarkCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.ConvertBookmarkExpenseCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.ConvertBookmarkIncomeCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.DeleteBookmarkCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.DeleteBookmarkExpenseCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.DeleteBookmarkIncomeCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.EditBookmarkCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.EditBookmarkExpenseCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.EditBookmarkIncomeCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.budget.SetExpenseLimitCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.budget.SetSavingsGoalCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.bookmarkparsers.AddBookmarkExpenseCommandParser;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.bookmarkparsers.AddBookmarkIncomeCommandParser;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.bookmarkparsers.ConvertBookmarkCommandParser;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.bookmarkparsers.DeleteBookmarkCommandParser;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.bookmarkparsers.EditBookmarkCommandParser;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.budgetparsers.SetExpenseLimitCommandParser;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.budgetparsers.SetSavingsGoalCommandParser;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState;
+import ay2021s1_cs2103_w16_3.finesse.ui.UiState.Tab;
+
+/**
+ * Parses user input.
+ */
+public class FinanceTrackerParser {
+
+ /**
+ * Command word for the generic "add" command which adds an expense or an income to the finance tracker
+ * depending on the tab the user is on.
+ */
+ public static final String ADD_COMMAND_COMMAND_WORD = "add";
+
+ /**
+ * Used for initial separation of command word and args.
+ */
+ private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)");
+
+ private final Logger logger = LogsCenter.getLogger(FinanceTrackerParser.class);
+
+ /**
+ * Parses user input into command for execution.
+ *
+ * @param userInput Full user input string.
+ * @param uiState Current state of the UI.
+ * @return The command based on the user input.
+ * @throws ParseException If the user input does not conform the expected format.
+ */
+ public Command parseCommand(String userInput, UiState uiState) throws ParseException {
+ // Replace non-breaking spaces with regular spaces and trim.
+ String trimmedUserInput = userInput.replaceAll("\u00A0", " ").trim();
+
+ final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(trimmedUserInput);
+ if (!matcher.matches()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE));
+ }
+
+ final Tab uiCurrentTab = uiState.getCurrentTab();
+ logger.info("----------------[CURRENT TAB][" + uiCurrentTab.toString() + "]");
+
+ final String commandWord = matcher.group("commandWord");
+ final String arguments = matcher.group("arguments");
+ switch (commandWord) {
+
+ case ADD_COMMAND_COMMAND_WORD:
+ switch (uiCurrentTab) {
+ case EXPENSES:
+ return new AddExpenseCommandParser().parse(arguments);
+ case INCOME:
+ return new AddIncomeCommandParser().parse(arguments);
+ default:
+ throw new ParseException(commandInvalidTabMessage(commandWord,
+ Tab.EXPENSES, Tab.INCOME));
+ }
+
+ case AddExpenseCommand.COMMAND_WORD:
+ case AddExpenseCommand.COMMAND_ALIAS:
+ return new AddExpenseCommandParser().parse(arguments);
+
+ case AddIncomeCommand.COMMAND_WORD:
+ case AddIncomeCommand.COMMAND_ALIAS:
+ return new AddIncomeCommandParser().parse(arguments);
+
+ case AddBookmarkExpenseCommand.COMMAND_WORD:
+ case AddBookmarkExpenseCommand.COMMAND_ALIAS:
+ return new AddBookmarkExpenseCommandParser().parse(arguments);
+
+ case AddBookmarkIncomeCommand.COMMAND_WORD:
+ case AddBookmarkIncomeCommand.COMMAND_ALIAS:
+ return new AddBookmarkIncomeCommandParser().parse(arguments);
+
+ case EditCommand.COMMAND_WORD:
+ switch (uiCurrentTab) {
+ case EXPENSES:
+ return new EditExpenseCommand(new EditCommandParser().parse(arguments));
+ case INCOME:
+ return new EditIncomeCommand(new EditCommandParser().parse(arguments));
+ default:
+ throw new ParseException(commandInvalidTabMessage(commandWord,
+ Tab.EXPENSES, Tab.INCOME));
+ }
+
+ case EditBookmarkCommand.COMMAND_WORD:
+ switch (uiCurrentTab) {
+ case EXPENSES:
+ return new EditBookmarkExpenseCommand(new EditBookmarkCommandParser().parse(arguments));
+ case INCOME:
+ return new EditBookmarkIncomeCommand(new EditBookmarkCommandParser().parse(arguments));
+ default:
+ throw new ParseException(commandInvalidTabMessage(commandWord,
+ Tab.EXPENSES, Tab.INCOME));
+ }
+
+ case DeleteCommand.COMMAND_WORD:
+ switch (uiCurrentTab) {
+ case EXPENSES:
+ return new DeleteExpenseCommand(new DeleteCommandParser().parse(arguments));
+ case INCOME:
+ return new DeleteIncomeCommand(new DeleteCommandParser().parse(arguments));
+ default:
+ throw new ParseException(commandInvalidTabMessage(commandWord,
+ Tab.EXPENSES, Tab.INCOME));
+ }
+
+ case DeleteBookmarkCommand.COMMAND_WORD:
+ switch (uiCurrentTab) {
+ case EXPENSES:
+ return new DeleteBookmarkExpenseCommand(new DeleteBookmarkCommandParser().parse(arguments));
+ case INCOME:
+ return new DeleteBookmarkIncomeCommand(new DeleteBookmarkCommandParser().parse(arguments));
+ default:
+ throw new ParseException(commandInvalidTabMessage(commandWord,
+ Tab.EXPENSES, Tab.INCOME));
+ }
+
+ case ConvertBookmarkCommand.COMMAND_WORD:
+ case ConvertBookmarkCommand.COMMAND_ALIAS:
+ switch (uiCurrentTab) {
+ case EXPENSES:
+ return new ConvertBookmarkExpenseCommand(new ConvertBookmarkCommandParser().parse(arguments));
+ case INCOME:
+ return new ConvertBookmarkIncomeCommand(new ConvertBookmarkCommandParser().parse(arguments));
+ default:
+ throw new ParseException(commandInvalidTabMessage(commandWord,
+ Tab.EXPENSES, Tab.INCOME));
+ }
+
+ case ClearCommand.COMMAND_WORD:
+ enforceNoArguments(ClearCommand.COMMAND_WORD, arguments);
+ return new ClearCommand();
+
+ case FindCommand.COMMAND_WORD:
+ switch (uiCurrentTab) {
+ case OVERVIEW:
+ return new FindTransactionCommand(new FindCommandParser().parse(arguments));
+ case EXPENSES:
+ return new FindExpenseCommand(new FindCommandParser().parse(arguments));
+ case INCOME:
+ return new FindIncomeCommand(new FindCommandParser().parse(arguments));
+ default:
+ throw new ParseException(commandInvalidTabMessage(commandWord,
+ Tab.OVERVIEW, Tab.EXPENSES, Tab.INCOME));
+ }
+
+ case SetExpenseLimitCommand.COMMAND_WORD:
+ case SetExpenseLimitCommand.COMMAND_ALIAS:
+ return new SetExpenseLimitCommandParser().parse(arguments);
+
+ case SetSavingsGoalCommand.COMMAND_WORD:
+ case SetSavingsGoalCommand.COMMAND_ALIAS:
+ return new SetSavingsGoalCommandParser().parse(arguments);
+
+ case ListCommand.COMMAND_WORD:
+ enforceNoArguments(ListCommand.COMMAND_WORD, arguments);
+ switch (uiCurrentTab) {
+ case OVERVIEW:
+ return new ListTransactionCommand();
+ case EXPENSES:
+ return new ListExpenseCommand();
+ case INCOME:
+ return new ListIncomeCommand();
+ default:
+ throw new ParseException(commandInvalidTabMessage(commandWord,
+ Tab.OVERVIEW, Tab.EXPENSES, Tab.INCOME));
+ }
+
+ case ListTransactionCommand.COMMAND_WORD:
+ case ListTransactionCommand.COMMAND_ALIAS:
+ enforceNoArguments(ListTransactionCommand.COMMAND_WORD, arguments);
+ return new ListTransactionCommand();
+
+ case ListExpenseCommand.COMMAND_WORD:
+ case ListExpenseCommand.COMMAND_ALIAS:
+ enforceNoArguments(ListExpenseCommand.COMMAND_WORD, arguments);
+ return new ListExpenseCommand();
+
+ case ListIncomeCommand.COMMAND_WORD:
+ case ListIncomeCommand.COMMAND_ALIAS:
+ enforceNoArguments(ListIncomeCommand.COMMAND_WORD, arguments);
+ return new ListIncomeCommand();
+
+ case ExitCommand.COMMAND_WORD:
+ enforceNoArguments(ExitCommand.COMMAND_WORD, arguments);
+ return new ExitCommand();
+
+ case HelpCommand.COMMAND_WORD:
+ enforceNoArguments(HelpCommand.COMMAND_WORD, arguments);
+ return new HelpCommand();
+
+ case TabCommand.COMMAND_WORD:
+ return new TabCommandParser().parse(arguments);
+
+ default:
+ throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
+ }
+ }
+
+ /**
+ * Error message to be used when a command is not applicable to the user's current tab.
+ *
+ * @param command The command word that was used incorrectly.
+ * @param tabs The tabs that the command is applicable to.
+ * @return The error message to be displayed to the user.
+ */
+ private String commandInvalidTabMessage(String command, Tab... tabs) {
+ return String.format(MESSAGE_INVALID_TAB_FORMAT, command,
+ Stream.of(tabs).map(Tab::toString).collect(Collectors.joining(", ")));
+ }
+
+ /**
+ * Enforces that no arguments are input for commands that take in no arguments.
+ *
+ * @param command The command word that was used.
+ * @param args The arguments that the user input.
+ * @throws ParseException If {@code args} is not the empty string.
+ */
+ private void enforceNoArguments(String command, String args) throws ParseException {
+ requireNonNull(command);
+ requireNonNull(args);
+ if (!args.isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_NO_ARGUMENTS_COMMAND_FORMAT, command));
+ }
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/FindCommandParser.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/FindCommandParser.java
new file mode 100644
index 00000000000..2e1dc315673
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/FindCommandParser.java
@@ -0,0 +1,206 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static ay2021s1_cs2103_w16_3.finesse.commons.util.StringUtil.isEmptyString;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT_FROM;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT_TO;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_CATEGORY;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_DATE;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_DATE_FROM;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_DATE_TO;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_TITLE;
+import static ay2021s1_cs2103_w16_3.finesse.model.category.Category.MESSAGE_EMPTY_CATEGORY;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Predicate;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.FindCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Date;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Transaction;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.predicates.HasCategoriesPredicate;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.predicates.HasExactAmountPredicate;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.predicates.InAmountRangePredicate;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.predicates.InDateRangePredicate;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.predicates.OnExactDatePredicate;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.predicates.TitleContainsKeyphrasesPredicate;
+
+/**
+ * Parses input arguments and creates a new FindCommand object
+ */
+public class FindCommandParser implements Parser {
+
+ public static final String MESSAGE_AMOUNT_SEARCH_USAGE =
+ "To search for transactions with an exact amount, please use " + PREFIX_AMOUNT
+ + " to indicate the amount.\n"
+ + "To search for transactions within a range of amounts, please use " + PREFIX_AMOUNT_FROM
+ + " to indicate the lower bound, and/or " + PREFIX_AMOUNT_TO + " to indicate the upper bound.";
+
+ public static final String MESSAGE_DATE_SEARCH_USAGE =
+ "To search for transactions with an exact date, please use " + PREFIX_DATE + " to indicate the date.\n"
+ + "To search for transactions within a range of dates, please use " + PREFIX_DATE_FROM
+ + " to indicate the lower bound, and/or " + PREFIX_DATE_TO + " to indicate the upper bound.";
+
+ public static final String MESSAGE_AMOUNT_SEARCH_CONSTRAINTS =
+ PREFIX_AMOUNT + " cannot be used concurrently with " + PREFIX_AMOUNT_FROM
+ + " and/or " + PREFIX_AMOUNT_TO + ".\n" + MESSAGE_AMOUNT_SEARCH_USAGE;
+
+ public static final String MESSAGE_DATE_SEARCH_CONSTRAINTS =
+ PREFIX_DATE + " cannot be used concurrently with " + PREFIX_DATE_FROM
+ + " and/or " + PREFIX_DATE_TO + ".\n" + MESSAGE_DATE_SEARCH_USAGE;
+
+ public static final String MESSAGE_AMOUNT_DUPLICATE_CONSTRAINTS =
+ "Only one amount can be input for a search of transactions containing an exact amount.\n"
+ + MESSAGE_AMOUNT_SEARCH_USAGE;
+
+ public static final String MESSAGE_DATE_DUPLICATE_CONSTRAINTS =
+ "Only one date can be input for a search of transactions occurring on an exact date.\n"
+ + MESSAGE_DATE_SEARCH_USAGE;
+
+ public static final String MESSAGE_AMOUNT_FROM_CONSTRAINTS =
+ "Only one amount can be input as the lower bound for a search of transactions within a range of amounts.\n"
+ + MESSAGE_AMOUNT_SEARCH_USAGE;
+
+ public static final String MESSAGE_AMOUNT_TO_CONSTRAINTS =
+ "Only one amount can be input as the upper bound for a search of transactions within a range of amounts.\n"
+ + MESSAGE_AMOUNT_SEARCH_USAGE;
+
+ public static final String MESSAGE_AMOUNT_RANGE_CONSTRAINTS =
+ "The lower bound for the amount range cannot be larger than the upper bound.";
+
+ public static final String MESSAGE_DATE_FROM_CONSTRAINTS =
+ "Only one date can be input as the lower bound for a search of transactions "
+ + "occurring within a range of dates.\n" + MESSAGE_DATE_SEARCH_USAGE;
+
+ public static final String MESSAGE_DATE_TO_CONSTRAINTS =
+ "Only one date can be input as the upper bound for a search of transactions "
+ + "occurring within a range of dates.\n" + MESSAGE_DATE_SEARCH_USAGE;
+
+ public static final String MESSAGE_DATE_RANGE_CONSTRAINTS =
+ "The lower bound for the date range cannot be later than the upper bound.";
+
+ public static final String MESSAGE_EMPTY_TITLE_KEYPHRASE = "Title keyphrases cannot be empty.";
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the FindCommand
+ * and returns a FindCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FindCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, FindCommand.MESSAGE_USAGE,
+ CliSyntax.getAllPrefixes());
+
+ List> predicateList = new ArrayList<>();
+
+ if (!argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_AMOUNT)) {
+ throw new ParseException(MESSAGE_AMOUNT_DUPLICATE_CONSTRAINTS);
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_DATE)) {
+ throw new ParseException(MESSAGE_DATE_DUPLICATE_CONSTRAINTS);
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_AMOUNT_FROM)) {
+ throw new ParseException(MESSAGE_AMOUNT_FROM_CONSTRAINTS);
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_AMOUNT_TO)) {
+ throw new ParseException(MESSAGE_AMOUNT_TO_CONSTRAINTS);
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_DATE_FROM)) {
+ throw new ParseException(MESSAGE_DATE_FROM_CONSTRAINTS);
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_DATE_TO)) {
+ throw new ParseException(MESSAGE_DATE_TO_CONSTRAINTS);
+ }
+
+ if (argMultimap.getValue(PREFIX_TITLE).isPresent()) {
+ List titleKeyphrases = argMultimap.getAllValues(PREFIX_TITLE);
+ for (String s: titleKeyphrases) {
+ if (isEmptyString(s)) {
+ throw new ParseException(MESSAGE_EMPTY_TITLE_KEYPHRASE);
+ }
+ }
+ predicateList.add(new TitleContainsKeyphrasesPredicate(titleKeyphrases));
+ }
+
+ if (argMultimap.getValue(PREFIX_AMOUNT).isPresent()) {
+ if (argMultimap.getValue(PREFIX_AMOUNT_FROM).isPresent()
+ || argMultimap.getValue(PREFIX_AMOUNT_TO).isPresent()) {
+ throw new ParseException(MESSAGE_AMOUNT_SEARCH_CONSTRAINTS);
+ }
+ Amount amount = ParserUtil.parseAmount(argMultimap.getValue(PREFIX_AMOUNT).get());
+ predicateList.add(new HasExactAmountPredicate(amount));
+ }
+
+ if (argMultimap.getValue(PREFIX_DATE).isPresent()) {
+ if (argMultimap.getValue(PREFIX_DATE_FROM).isPresent()
+ || argMultimap.getValue(PREFIX_DATE_TO).isPresent()) {
+ throw new ParseException(MESSAGE_DATE_SEARCH_CONSTRAINTS);
+ }
+ Date date = ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE).get());
+ predicateList.add(new OnExactDatePredicate(date));
+ }
+
+ if (argMultimap.getValue(PREFIX_CATEGORY).isPresent()) {
+ List categories = argMultimap.getAllValues(PREFIX_CATEGORY);
+ for (String s: categories) {
+ if (isEmptyString(s)) {
+ throw new ParseException(MESSAGE_EMPTY_CATEGORY);
+ }
+ }
+ predicateList.add(new HasCategoriesPredicate(categories));
+ }
+
+ if (argMultimap.getValue(PREFIX_AMOUNT_FROM).isPresent()
+ || argMultimap.getValue(PREFIX_AMOUNT_TO).isPresent()) {
+ Amount amountFrom = null;
+ Amount amountTo = null;
+ if (argMultimap.getValue(PREFIX_AMOUNT_FROM).isPresent()) {
+ amountFrom = ParserUtil.parseAmount(argMultimap.getValue(PREFIX_AMOUNT_FROM).get());
+ }
+ if (argMultimap.getValue(PREFIX_AMOUNT_TO).isPresent()) {
+ amountTo = ParserUtil.parseAmount(argMultimap.getValue(PREFIX_AMOUNT_TO).get());
+ }
+ if (amountFrom != null && amountTo != null && amountFrom.compareTo(amountTo) > 0) {
+ throw new ParseException(MESSAGE_AMOUNT_RANGE_CONSTRAINTS);
+ }
+ predicateList.add(new InAmountRangePredicate(Optional.ofNullable(amountFrom),
+ Optional.ofNullable(amountTo)));
+ }
+
+ if (argMultimap.getValue(PREFIX_DATE_FROM).isPresent()
+ || argMultimap.getValue(PREFIX_DATE_TO).isPresent()) {
+ Date dateFrom = null;
+ Date dateTo = null;
+ if (argMultimap.getValue(PREFIX_DATE_FROM).isPresent()) {
+ dateFrom = ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE_FROM).get());
+ }
+ if (argMultimap.getValue(PREFIX_DATE_TO).isPresent()) {
+ dateTo = ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE_TO).get());
+ }
+ if (dateFrom != null && dateTo != null && dateFrom.compareTo(dateTo) > 0) {
+ throw new ParseException(MESSAGE_DATE_RANGE_CONSTRAINTS);
+ }
+ predicateList.add(new InDateRangePredicate(Optional.ofNullable(dateFrom),
+ Optional.ofNullable(dateTo)));
+ }
+
+ if (predicateList.isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
+ }
+
+ return new FindCommand(predicateList);
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/Parser.java
similarity index 66%
rename from src/main/java/seedu/address/logic/parser/Parser.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/Parser.java
index d6551ad8e3f..7f3adc9485a 100644
--- a/src/main/java/seedu/address/logic/parser/Parser.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/Parser.java
@@ -1,7 +1,7 @@
-package seedu.address.logic.parser;
+package ay2021s1_cs2103_w16_3.finesse.logic.parser;
-import seedu.address.logic.commands.Command;
-import seedu.address.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.Command;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
/**
* Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}.
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/ParserUtil.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/ParserUtil.java
new file mode 100644
index 00000000000..1538fb3b806
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/ParserUtil.java
@@ -0,0 +1,245 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static ay2021s1_cs2103_w16_3.finesse.commons.util.StringUtil.isEmptyString;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_CATEGORY;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_DATE;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_TITLE;
+import static ay2021s1_cs2103_w16_3.finesse.model.category.Category.MESSAGE_EMPTY_CATEGORY;
+import static ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount.MESSAGE_EMPTY_AMOUNT;
+import static ay2021s1_cs2103_w16_3.finesse.model.transaction.Date.MESSAGE_EMPTY_DATE;
+import static ay2021s1_cs2103_w16_3.finesse.model.transaction.Title.MESSAGE_EMPTY_TITLE;
+import static java.util.Objects.requireNonNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.index.Index;
+import ay2021s1_cs2103_w16_3.finesse.commons.util.StringUtil;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.bookmarkparsers.BookmarkTransactionBuilder;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkTransaction;
+import ay2021s1_cs2103_w16_3.finesse.model.category.Category;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Date;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Title;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Transaction;
+
+/**
+ * Contains utility methods used for parsing strings in the various *Parser classes.
+ */
+public class ParserUtil {
+
+ public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer.";
+
+ /**
+ * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be
+ * trimmed.
+ * @throws ParseException if the specified index is invalid (not non-zero unsigned integer).
+ */
+ public static Index parseIndex(String oneBasedIndex) throws ParseException {
+ String trimmedIndex = oneBasedIndex.trim();
+ if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) {
+ throw new ParseException(MESSAGE_INVALID_INDEX);
+ }
+ return Index.fromOneBased(Integer.parseInt(trimmedIndex));
+ }
+
+ /**
+ * Parses a {@code String title} into a {@code Title}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code title} is invalid.
+ */
+ public static Title parseTitle(String title) throws ParseException {
+ requireNonNull(title);
+ String trimmedTitle = title.trim();
+ if (isEmptyString(trimmedTitle)) {
+ throw new ParseException(MESSAGE_EMPTY_TITLE);
+ }
+ if (!Title.isValidTitle(trimmedTitle)) {
+ throw new ParseException(Title.MESSAGE_CONSTRAINTS);
+ }
+ return new Title(trimmedTitle);
+ }
+
+ /**
+ * Parses a {@code String title} into a {@code Title}.
+ * Leading and trailing whitespaces will be trimmed.
+ * Any extra whitespaces between words will be removed.
+ *
+ * @throws ParseException if the given {@code title} is invalid.
+ */
+ public static Title parseTitleAndTrimBetweenWords(String title) throws ParseException {
+ requireNonNull(title);
+ String removedExtraWhitespaceTitle = title.replaceAll("\\s{2,}", " ");
+ return parseTitle(removedExtraWhitespaceTitle);
+ }
+
+ /**
+ * Parses a {@code String amount} into a {@code Amount}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code amount} is invalid.
+ */
+ public static Amount parseAmount(String amount) throws ParseException {
+ requireNonNull(amount);
+ String trimmedAmount = amount.trim();
+ if (isEmptyString(trimmedAmount)) {
+ throw new ParseException(MESSAGE_EMPTY_AMOUNT);
+ }
+ if (!Amount.isValidAmount(trimmedAmount)) {
+ throw new ParseException(Amount.MESSAGE_CONSTRAINTS);
+ }
+ return new Amount(trimmedAmount);
+ }
+
+ /**
+ * Parses a {@code String date} into an {@code Date}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code date} is invalid.
+ */
+ public static Date parseDate(String date) throws ParseException {
+ requireNonNull(date);
+ String trimmedDate = date.trim();
+ if (isEmptyString(trimmedDate)) {
+ throw new ParseException(MESSAGE_EMPTY_DATE);
+ }
+ if (!Date.isValidDate(trimmedDate)) {
+ throw new ParseException(Date.MESSAGE_CONSTRAINTS);
+ }
+ return new Date(trimmedDate);
+ }
+
+ /**
+ * Parses a {@code String category} into a {@code Category}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code category} is invalid.
+ */
+ public static Category parseCategory(String category) throws ParseException {
+ requireNonNull(category);
+ String trimmedCategory = category.trim();
+ if (isEmptyString(trimmedCategory)) {
+ throw new ParseException(MESSAGE_EMPTY_CATEGORY);
+ }
+ if (!Category.isValidCategoryName(trimmedCategory)) {
+ throw new ParseException(Category.MESSAGE_CONSTRAINTS);
+ }
+ return new Category(trimmedCategory);
+ }
+
+ /**
+ * Parses {@code Collection categories} into a {@code Set}.
+ */
+ public static Set parseCategories(Collection categories) throws ParseException {
+ requireNonNull(categories);
+ final Set categorySet = new HashSet<>();
+ for (String categoryName : categories) {
+ categorySet.add(parseCategory(categoryName));
+ }
+ return categorySet;
+ }
+
+ /**
+ * Parses {@code Collection categories} into a {@code Set} if {@code categories} is non-empty.
+ * If {@code categories} contain only one element which is an empty string, it will be parsed into a
+ * {@code Set} containing zero categories.
+ */
+ public static Optional> parseCategoriesForEdit(Collection categories) throws ParseException {
+ assert categories != null;
+
+ if (categories.isEmpty()) {
+ return Optional.empty();
+ }
+ Collection categorySet = categories.size() == 1 && categories.contains("")
+ ? Collections.emptySet() : categories;
+ return Optional.of(ParserUtil.parseCategories(categorySet));
+ }
+
+ /**
+ * Parses {@code String args} into a {@code BookmarkTransactionBuilder}.
+ *
+ * @throws ParseException if the given {@code args} do not adhere to the format of a bookmark transaction.
+ */
+ public static BookmarkTransactionBuilder parseBookmarkTransactionBuilder(String args, String exceptionMessage)
+ throws ParseException {
+ requireNonNull(args);
+ requireNonNull(exceptionMessage);
+
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, exceptionMessage, PREFIX_TITLE, PREFIX_AMOUNT,
+ PREFIX_CATEGORY);
+
+ if (!argMultimap.getPreamble().isEmpty() || !argMultimap.arePrefixesPresent(PREFIX_TITLE, PREFIX_AMOUNT)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, exceptionMessage));
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_TITLE)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, BookmarkTransaction.MESSAGE_TITLE_CONSTRAINTS));
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_AMOUNT)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, BookmarkTransaction.MESSAGE_AMOUNT_CONSTRAINTS));
+ }
+
+ Title title = ParserUtil.parseTitleAndTrimBetweenWords(argMultimap.getValue(PREFIX_TITLE).get());
+ Amount amount = ParserUtil.parseAmount(argMultimap.getValue(PREFIX_AMOUNT).get());
+ Set categoryList = ParserUtil.parseCategories(argMultimap.getAllValues(PREFIX_CATEGORY));
+
+ return new BookmarkTransactionBuilder(title, amount, categoryList);
+ }
+
+ /**
+ * Parses {@code String args} into a {@code TransactionBuilder}.
+ *
+ * @throws ParseException if the given {@code args} do not adhere to the format of a transaction.
+ */
+ public static TransactionBuilder parseTransactionBuilder(String args, String exceptionMessage)
+ throws ParseException {
+ requireNonNull(args);
+ requireNonNull(exceptionMessage);
+
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, exceptionMessage, PREFIX_TITLE, PREFIX_AMOUNT,
+ PREFIX_DATE, PREFIX_CATEGORY);
+
+ if (!argMultimap.getPreamble().isEmpty() || !argMultimap.arePrefixesPresent(PREFIX_TITLE, PREFIX_AMOUNT)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, exceptionMessage));
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_TITLE)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, Transaction.MESSAGE_TITLE_CONSTRAINTS));
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_AMOUNT)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, Transaction.MESSAGE_AMOUNT_CONSTRAINTS));
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_DATE)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, Transaction.MESSAGE_DATE_CONSTRAINTS));
+ }
+
+ Title title = ParserUtil.parseTitleAndTrimBetweenWords(argMultimap.getValue(PREFIX_TITLE).get());
+ Amount amount = ParserUtil.parseAmount(argMultimap.getValue(PREFIX_AMOUNT).get());
+ Set categoryList = ParserUtil.parseCategories(argMultimap.getAllValues(PREFIX_CATEGORY));
+
+ Date date;
+ if (argMultimap.getValue(PREFIX_DATE).isPresent()) {
+ date = ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE).get());
+ } else {
+ date = Date.getCurrentDate();
+ }
+
+ return new TransactionBuilder(title, amount, date, categoryList);
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/Prefix.java
similarity index 67%
rename from src/main/java/seedu/address/logic/parser/Prefix.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/Prefix.java
index c859d5fa5db..32517509a20 100644
--- a/src/main/java/seedu/address/logic/parser/Prefix.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/Prefix.java
@@ -1,4 +1,6 @@
-package seedu.address.logic.parser;
+package ay2021s1_cs2103_w16_3.finesse.logic.parser;
+
+import static java.util.Objects.requireNonNull;
/**
* A prefix that marks the beginning of an argument in an arguments string.
@@ -7,7 +9,14 @@
public class Prefix {
private final String prefix;
+ /**
+ * Constructs a new {@code Prefix} object with the specified prefix.
+ * Note that the prefix cannot be {@code null}.
+ *
+ * @param prefix The specified prefix.
+ */
public Prefix(String prefix) {
+ requireNonNull(prefix);
this.prefix = prefix;
}
@@ -21,7 +30,7 @@ public String toString() {
@Override
public int hashCode() {
- return prefix == null ? 0 : prefix.hashCode();
+ return prefix.hashCode();
}
@Override
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/TabCommandParser.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/TabCommandParser.java
new file mode 100644
index 00000000000..84400f1c4ef
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/TabCommandParser.java
@@ -0,0 +1,27 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.index.Index;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.TabCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new {@code TabCommand} object.
+ */
+public class TabCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the {@code TabCommand}
+ * and returns a {@code TabCommand} object for execution.
+ * @throws ParseException If the user input does not conform to the expected format.
+ */
+ public TabCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new TabCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, TabCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/TransactionBuilder.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/TransactionBuilder.java
new file mode 100644
index 00000000000..e2011fdb207
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/TransactionBuilder.java
@@ -0,0 +1,120 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import ay2021s1_cs2103_w16_3.finesse.model.category.Category;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Date;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Expense;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Income;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Title;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Transaction;
+import ay2021s1_cs2103_w16_3.finesse.model.util.SampleDataUtil;
+
+/**
+ * A utility class to help with building Transaction objects.
+ */
+public class TransactionBuilder {
+
+ public static final String DEFAULT_TITLE = "Bubble Tea";
+ public static final String DEFAULT_AMOUNT = "$4.80";
+ public static final String DEFAULT_DATE = "14/10/2020";
+
+ private Title title;
+ private Amount amount;
+ private Date date;
+ private Set categories;
+
+ /**
+ * Creates a {@code TransactionBuilder} with the given {@code Title}, {@code Amount}, {@code Date} and
+ * {@code Category}.
+ */
+ public TransactionBuilder(Title title, Amount amount, Date date, Set categories) {
+ this.title = title;
+ this.amount = amount;
+ this.date = date;
+ this.categories = categories;
+ }
+
+ /**
+ * Creates a {@code TransactionBuilder} with the default details.
+ */
+ public TransactionBuilder() {
+ title = new Title(DEFAULT_TITLE);
+ amount = new Amount(DEFAULT_AMOUNT);
+ date = new Date(DEFAULT_DATE);
+ categories = new HashSet<>();
+ }
+
+ /**
+ * Initializes the TransactionBuilder with the data of {@code transactionToCopy}.
+ */
+ public TransactionBuilder(Transaction transactionToCopy) {
+ title = transactionToCopy.getTitle();
+ amount = transactionToCopy.getAmount();
+ date = transactionToCopy.getDate();
+ categories = new HashSet<>(transactionToCopy.getCategories());
+ }
+
+ /**
+ * Sets the {@code Title} of the {@code Transaction} that we are building.
+ */
+ public TransactionBuilder withTitle(String title) {
+ this.title = new Title(title);
+ return this;
+ }
+
+ /**
+ * Parses the {@code categories} into a {@code Set} and set it to the {@code Transaction} that we are
+ * building.
+ */
+ public TransactionBuilder withCategories(String ... categories) {
+ this.categories = SampleDataUtil.getCategoriesSet(categories);
+ return this;
+ }
+
+ /**
+ * Sets the {@code Amount} of the {@code Transaction} that we are building.
+ */
+ public TransactionBuilder withAmount(String amount) {
+ this.amount = new Amount(amount);
+ return this;
+ }
+
+ /**
+ * Sets the {@code Date} of the {@code Transaction} that we are building.
+ */
+ public TransactionBuilder withDate(String date) {
+ this.date = new Date(date);
+ return this;
+ }
+
+ public Income buildIncome() {
+ return new Income(title, amount, date, categories);
+ }
+
+ public Expense buildExpense() {
+ return new Expense(title, amount, date, categories);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // Short circuit if same object.
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls.
+ if (!(other instanceof TransactionBuilder)) {
+ return false;
+ }
+
+ TransactionBuilder otherTransactionBuilder = (TransactionBuilder) other;
+ return title.equals(otherTransactionBuilder.title)
+ && amount.equals(otherTransactionBuilder.amount)
+ && date.equals(otherTransactionBuilder.date)
+ && categories.equals(otherTransactionBuilder.categories);
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/AddBookmarkExpenseCommandParser.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/AddBookmarkExpenseCommandParser.java
new file mode 100644
index 00000000000..10ae21c5b03
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/AddBookmarkExpenseCommandParser.java
@@ -0,0 +1,26 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser.bookmarkparsers;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.AddBookmarkExpenseCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.Parser;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.ParserUtil;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkExpense;
+
+/**
+ * Parses input arguments and creates a new AddBookmarkExpenseCommand object
+ */
+public class AddBookmarkExpenseCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddBookmarkExpenseCommand
+ * and returns an AddBookmarkExpenseCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AddBookmarkExpenseCommand parse(String args) throws ParseException {
+ BookmarkTransactionBuilder bookmarkTransactionBuilder = ParserUtil
+ .parseBookmarkTransactionBuilder(args, AddBookmarkExpenseCommand.MESSAGE_USAGE);
+ BookmarkExpense toAdd = bookmarkTransactionBuilder.buildBookmarkExpense();
+ return new AddBookmarkExpenseCommand(toAdd);
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/AddBookmarkIncomeCommandParser.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/AddBookmarkIncomeCommandParser.java
new file mode 100644
index 00000000000..272f399d9bf
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/AddBookmarkIncomeCommandParser.java
@@ -0,0 +1,26 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser.bookmarkparsers;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.AddBookmarkIncomeCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.Parser;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.ParserUtil;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkIncome;
+
+/**
+ * Parses input arguments and creates a new AddBookmarkIncomeCommand object
+ */
+public class AddBookmarkIncomeCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddBookmarkIncomeCommand
+ * and returns an AddBookmarkIncomeCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AddBookmarkIncomeCommand parse(String args) throws ParseException {
+ BookmarkTransactionBuilder bookmarkTransactionBuilder = ParserUtil
+ .parseBookmarkTransactionBuilder(args, AddBookmarkIncomeCommand.MESSAGE_USAGE);
+ BookmarkIncome toAdd = bookmarkTransactionBuilder.buildBookmarkIncome();
+ return new AddBookmarkIncomeCommand(toAdd);
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/BookmarkTransactionBuilder.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/BookmarkTransactionBuilder.java
new file mode 100644
index 00000000000..9f7eee08301
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/BookmarkTransactionBuilder.java
@@ -0,0 +1,104 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser.bookmarkparsers;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkExpense;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkIncome;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkTransaction;
+import ay2021s1_cs2103_w16_3.finesse.model.category.Category;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Title;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Transaction;
+import ay2021s1_cs2103_w16_3.finesse.model.util.SampleDataUtil;
+
+/**
+ * A utility class to help with building BookmarkExpense and BookmarkIncome objects.
+ */
+public class BookmarkTransactionBuilder {
+
+ public static final String DEFAULT_TITLE = "Phone Bill";
+ public static final String DEFAULT_AMOUNT = "$60.00";
+
+ private Title title;
+ private Amount amount;
+ private Set categories;
+
+ /**
+ * Creates a {@code BookmarkTransactionBuilder} with the given {@code Title}, {@code Amount} and {@code Category}.
+ */
+ public BookmarkTransactionBuilder(Title title, Amount amount, Set categories) {
+ this.title = title;
+ this.amount = amount;
+ this.categories = categories;
+ }
+
+ /**
+ * Creates a {@code BookmarkTransactionBuilder} with the default details.
+ */
+ public BookmarkTransactionBuilder() {
+ title = new Title(DEFAULT_TITLE);
+ amount = new Amount(DEFAULT_AMOUNT);
+ categories = new HashSet<>();
+ }
+
+ /**
+ * Initializes the BookmarkTransactionBuilder with the data of {@code bookmarkTransactionToCopy}.
+ */
+ public BookmarkTransactionBuilder(BookmarkTransaction bookmarkTransactionToCopy) {
+ title = bookmarkTransactionToCopy.getTitle();
+ amount = bookmarkTransactionToCopy.getAmount();
+ categories = new HashSet<>(bookmarkTransactionToCopy.getCategories());
+ }
+
+ /**
+ * Sets the {@code Title} of the {@code BookmarkTransaction} that we are building.
+ */
+ public BookmarkTransactionBuilder withTitle(String title) {
+ this.title = new Title(title);
+ return this;
+ }
+
+ /**
+ * Parses the {@code categories} into a {@code Set} and set it to the {@code BookmarkTransaction}
+ * that we are building.
+ */
+ public BookmarkTransactionBuilder withCategories(String ... categories) {
+ this.categories = SampleDataUtil.getCategoriesSet(categories);
+ return this;
+ }
+
+ /**
+ * Sets the {@code Amount} of the {@code BookmarkTransaction} that we are building.
+ */
+ public BookmarkTransactionBuilder withAmount(String amount) {
+ this.amount = new Amount(amount);
+ return this;
+ }
+
+ public BookmarkExpense buildBookmarkExpense() {
+ return new BookmarkExpense(title, amount, categories);
+ }
+
+ public BookmarkIncome buildBookmarkIncome() {
+ return new BookmarkIncome(title, amount, categories);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // Short circuit if same object.
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls.
+ if (!(other instanceof BookmarkTransactionBuilder)) {
+ return false;
+ }
+
+ BookmarkTransactionBuilder otherBookmarkTransactionBuilder = (BookmarkTransactionBuilder) other;
+ return title.equals(otherBookmarkTransactionBuilder.title)
+ && amount.equals(otherBookmarkTransactionBuilder.amount)
+ && categories.equals(otherBookmarkTransactionBuilder.categories);
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/ConvertBookmarkCommandParser.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/ConvertBookmarkCommandParser.java
new file mode 100644
index 00000000000..40194d27ac1
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/ConvertBookmarkCommandParser.java
@@ -0,0 +1,55 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser.bookmarkparsers;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_DATE;
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.index.Index;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.ConvertBookmarkCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.ArgumentMultimap;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.ArgumentTokenizer;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.Parser;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.ParserUtil;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Date;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Transaction;
+
+/**
+ * Parses input arguments and creates a new ConvertBookmarkCommand object
+ */
+public class ConvertBookmarkCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ConvertBookmarkCommand
+ * and returns an ConvertBookmarkCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ConvertBookmarkCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, ConvertBookmarkCommand.MESSAGE_USAGE,
+ PREFIX_DATE);
+
+ Index index;
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ ConvertBookmarkCommand.MESSAGE_USAGE), pe);
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_DATE)) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ Transaction.MESSAGE_DATE_CONSTRAINTS));
+ }
+
+ Date date;
+ if (argMultimap.getValue(PREFIX_DATE).isPresent()) {
+ date = ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE).get());
+ } else {
+ date = Date.getCurrentDate();
+ }
+
+ return new ConvertBookmarkCommand(index, date);
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/DeleteBookmarkCommandParser.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/DeleteBookmarkCommandParser.java
new file mode 100644
index 00000000000..ff759522b56
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/DeleteBookmarkCommandParser.java
@@ -0,0 +1,30 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser.bookmarkparsers;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.index.Index;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.DeleteBookmarkCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.Parser;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.ParserUtil;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteBookmarkCommand object
+ */
+public class DeleteBookmarkCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteBookmarkCommand
+ * and returns a DeleteBookmarkCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteBookmarkCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new DeleteBookmarkCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteBookmarkCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/EditBookmarkCommandParser.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/EditBookmarkCommandParser.java
new file mode 100644
index 00000000000..3613319cc3e
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/bookmarkparsers/EditBookmarkCommandParser.java
@@ -0,0 +1,72 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser.bookmarkparsers;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_CATEGORY;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_TITLE;
+import static java.util.Objects.requireNonNull;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.index.Index;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.EditBookmarkCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.bookmark.EditBookmarkTransactionDescriptor;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.ArgumentMultimap;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.ArgumentTokenizer;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.Parser;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.ParserUtil;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkTransaction;
+
+/**
+ * Parses input arguments and creates a new EditBookmarkCommand object
+ */
+public class EditBookmarkCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EditBookmarkIncomeCommand
+ * and returns an EditBookmarkIncomeCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EditBookmarkCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, EditBookmarkCommand.MESSAGE_USAGE, PREFIX_TITLE,
+ PREFIX_AMOUNT, PREFIX_CATEGORY);
+
+ Index index;
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditBookmarkCommand.MESSAGE_USAGE),
+ pe);
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_TITLE)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, BookmarkTransaction.MESSAGE_TITLE_CONSTRAINTS));
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_AMOUNT)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, BookmarkTransaction.MESSAGE_AMOUNT_CONSTRAINTS));
+ }
+
+ EditBookmarkTransactionDescriptor editBookmarkIncomeDescriptor = new EditBookmarkTransactionDescriptor();
+
+ if (argMultimap.getValue(PREFIX_TITLE).isPresent()) {
+ editBookmarkIncomeDescriptor.setTitle(ParserUtil
+ .parseTitleAndTrimBetweenWords(argMultimap.getValue(PREFIX_TITLE).get()));
+ }
+ if (argMultimap.getValue(PREFIX_AMOUNT).isPresent()) {
+ editBookmarkIncomeDescriptor.setAmount(ParserUtil.parseAmount(argMultimap.getValue(PREFIX_AMOUNT).get()));
+ }
+
+ ParserUtil.parseCategoriesForEdit(argMultimap.getAllValues(PREFIX_CATEGORY))
+ .ifPresent(editBookmarkIncomeDescriptor::setCategories);
+
+ if (!editBookmarkIncomeDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditBookmarkCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EditBookmarkCommand(index, editBookmarkIncomeDescriptor);
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/budgetparsers/SetExpenseLimitCommandParser.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/budgetparsers/SetExpenseLimitCommandParser.java
new file mode 100644
index 00000000000..ab6d2467a4b
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/budgetparsers/SetExpenseLimitCommandParser.java
@@ -0,0 +1,43 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser.budgetparsers;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.budget.SetExpenseLimitCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.ArgumentMultimap;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.ArgumentTokenizer;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.Parser;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.ParserUtil;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+
+/**
+ * Parses input arguments and creates a new SetExpenseLimitCommand object.
+ */
+public class SetExpenseLimitCommandParser implements Parser {
+
+ public static final String MESSAGE_CONSTRAINTS = "Only one amount should be input as the expense limit.";
+ /**
+ * Parses the given {@code String} of arguments in the context of the SetExpenseLimitCommand
+ * and returns a SetExpenseLimitCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public SetExpenseLimitCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, SetExpenseLimitCommand.MESSAGE_USAGE,
+ PREFIX_AMOUNT);
+
+ if (!argMultimap.arePrefixesPresent(PREFIX_AMOUNT) || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, SetExpenseLimitCommand.MESSAGE_USAGE));
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_AMOUNT)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_CONSTRAINTS));
+ }
+
+ Amount amount = ParserUtil.parseAmount(argMultimap.getValue(PREFIX_AMOUNT).get());
+ return new SetExpenseLimitCommand(amount);
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/budgetparsers/SetSavingsGoalCommandParser.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/budgetparsers/SetSavingsGoalCommandParser.java
new file mode 100644
index 00000000000..cc950b9709c
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/budgetparsers/SetSavingsGoalCommandParser.java
@@ -0,0 +1,45 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.parser.budgetparsers;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static ay2021s1_cs2103_w16_3.finesse.logic.parser.CliSyntax.PREFIX_AMOUNT;
+
+import ay2021s1_cs2103_w16_3.finesse.logic.commands.budget.SetSavingsGoalCommand;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.ArgumentMultimap;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.ArgumentTokenizer;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.Parser;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.ParserUtil;
+import ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions.ParseException;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+
+/**
+ * Parses input arguments and creates a new SetSavingsGoalCommand object.
+ */
+public class SetSavingsGoalCommandParser implements Parser {
+
+ public static final String MESSAGE_CONSTRAINTS = "Only one amount should be input as the savings goal.";
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the SetSavingsGoalCommand
+ * and returns a SetSavingsGoalCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public SetSavingsGoalCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, SetSavingsGoalCommand.MESSAGE_USAGE,
+ PREFIX_AMOUNT);
+
+ if (!argMultimap.arePrefixesPresent(PREFIX_AMOUNT)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, SetSavingsGoalCommand.MESSAGE_USAGE));
+ }
+
+ if (argMultimap.moreThanOneValuePresent(PREFIX_AMOUNT)) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_CONSTRAINTS));
+ }
+
+ Amount amount = ParserUtil.parseAmount(argMultimap.getValue(PREFIX_AMOUNT).get());
+ return new SetSavingsGoalCommand(amount);
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/exceptions/ParseException.java
similarity index 67%
rename from src/main/java/seedu/address/logic/parser/exceptions/ParseException.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/exceptions/ParseException.java
index 158a1a54c1c..96928b9bef9 100644
--- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/parser/exceptions/ParseException.java
@@ -1,6 +1,6 @@
-package seedu.address.logic.parser.exceptions;
+package ay2021s1_cs2103_w16_3.finesse.logic.parser.exceptions;
-import seedu.address.commons.exceptions.IllegalValueException;
+import ay2021s1_cs2103_w16_3.finesse.commons.exceptions.IllegalValueException;
/**
* Represents a parse error encountered by a parser.
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/time/Clock.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/time/Clock.java
new file mode 100644
index 00000000000..3e3de43d350
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/time/Clock.java
@@ -0,0 +1,8 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.time;
+
+/**
+ * Represents a clock that is able to give the current time.
+ */
+public interface Clock {
+ long getCurrentTime();
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/time/SystemClock.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/time/SystemClock.java
new file mode 100644
index 00000000000..4d0ce972066
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/time/SystemClock.java
@@ -0,0 +1,17 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.time;
+
+/**
+ * A clock that relies on the system time.
+ */
+public class SystemClock implements Clock {
+ /**
+ * Returns the current system time in milliseconds.
+ *
+ * @return The difference, measured in milliseconds, between the current system
+ * time and midnight, January 1, 1970 UTC.
+ */
+ @Override
+ public long getCurrentTime() {
+ return System.currentTimeMillis();
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/time/Timekeeper.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/time/Timekeeper.java
new file mode 100644
index 00000000000..88b2a416932
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/time/Timekeeper.java
@@ -0,0 +1,63 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.time;
+
+import java.util.logging.Logger;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.LogsCenter;
+import ay2021s1_cs2103_w16_3.finesse.logic.time.exceptions.TemporalException;
+
+/**
+ * Responsible for detecting temporal disruptions.
+ */
+public class Timekeeper {
+ // Strings
+ private static final String TIME_INCONSISTENT =
+ "The current time '%s' is earlier than the last observed time '%s'!";
+ private static final String TIME_INITIALIZED = "Current time initialized to '%s'";
+ private static final String TIME_UPDATED = "Current time updated to '%s'";
+ private static final String TIME_IRREGULARITIES_DETECTED = "Irregularities in the flow of time detected.";
+
+ /** Logger for logging time updates. */
+ private static final Logger logger = LogsCenter.getLogger(Timekeeper.class);
+ /** A clock from which to poll the current time. */
+ private final Clock clock;
+ /** The last observed time. */
+ private long lastObservedTime;
+
+ /**
+ * Constructs a new {@code Timekeeper} with the default system time clock
+ * and the current system time.
+ */
+ public Timekeeper() {
+ this(new SystemClock());
+ }
+
+ /**
+ * Constructs a new {@code Timekeeper} with the specified clock and the
+ * current time provided by the clock.
+ *
+ * @param clock The {@code Clock} to poll for the current time.
+ */
+ public Timekeeper(Clock clock) {
+ this.clock = clock;
+ lastObservedTime = clock.getCurrentTime();
+ logger.info(String.format(TIME_INITIALIZED, lastObservedTime));
+ }
+
+ /**
+ * Checks in with the {@code Timekeeper}. If the current time is earlier than
+ * the last observed time, this method throws a {@code TemporalException}.
+ * Otherwise, it updates the last observed time of the {@code Timekeeper}.
+ *
+ * @throws TemporalException If the current time is earlier than the last
+ * observed time.
+ */
+ public void checkIn() throws TemporalException {
+ long currentTime = clock.getCurrentTime();
+ if (currentTime < lastObservedTime) {
+ logger.severe(String.format(TIME_INCONSISTENT, currentTime, lastObservedTime));
+ throw new TemporalException(TIME_IRREGULARITIES_DETECTED);
+ }
+ lastObservedTime = currentTime;
+ logger.info(String.format(TIME_UPDATED, lastObservedTime));
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/time/exceptions/TemporalException.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/time/exceptions/TemporalException.java
new file mode 100644
index 00000000000..e77d4ba4b73
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/logic/time/exceptions/TemporalException.java
@@ -0,0 +1,24 @@
+package ay2021s1_cs2103_w16_3.finesse.logic.time.exceptions;
+
+/**
+ * Represents a disruption in the flow of time.
+ */
+public class TemporalException extends Exception {
+ /**
+ * Constructs a new {@code TemporalException} with the specified message.
+ * @param message The message to use for this exception, may be {@code null}.
+ */
+ public TemporalException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code TemporalException} with the specified message and cause.
+ *
+ * @param message The message to use for this exception, may be {@code null}.
+ * @param cause The cause of the exception, may be {@code null}.
+ */
+ public TemporalException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/FinanceTracker.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/FinanceTracker.java
new file mode 100644
index 00000000000..65c2ceb567a
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/FinanceTracker.java
@@ -0,0 +1,249 @@
+package ay2021s1_cs2103_w16_3.finesse.model;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkExpense;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkExpenseList;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkIncome;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkIncomeList;
+import ay2021s1_cs2103_w16_3.finesse.model.budget.MonthlyBudget;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Expense;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Income;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Transaction;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.TransactionList;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+/**
+ * Wraps all data at the finance tracker level
+ */
+public class FinanceTracker implements ReadOnlyFinanceTracker {
+
+ private static final int NUM_OF_MONTHS = 3;
+
+ private final TransactionList transactions;
+ private final BookmarkExpenseList bookmarkExpenses;
+ private final BookmarkIncomeList bookmarkIncomes;
+ private final MonthlyBudget monthlyBudget;
+
+ /*
+ * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication
+ * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html
+ *
+ * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication
+ * among constructors.
+ */
+ {
+ transactions = new TransactionList();
+ bookmarkExpenses = new BookmarkExpenseList();
+ bookmarkIncomes = new BookmarkIncomeList();
+ monthlyBudget = new MonthlyBudget();
+ }
+
+ public FinanceTracker() {}
+
+ /**
+ * Creates an FinanceTracker using the Transactions in the {@code toBeCopied}
+ */
+ public FinanceTracker(ReadOnlyFinanceTracker toBeCopied) {
+ this();
+ resetData(toBeCopied);
+ }
+
+ //// list overwrite operations
+
+ /**
+ * Replaces the contents of the transaction list with {@code transactions}.
+ */
+ public void setTransactions(List transactions) {
+ this.transactions.setTransactions(transactions);
+ }
+
+ /**
+ * Replaces the contents of the bookmark expense list with {@code bookmarkExpens}.
+ */
+ public void setBookmarkExpenses(List bookmarkExpens) {
+ this.bookmarkExpenses.setBookmarkExpenses(bookmarkExpens);
+ }
+
+ /**
+ * Replaces the contents of the bookmark incomes list with {@code bookmarkIncomes}.
+ */
+ public void setBookmarkIncomes(List bookmarkIncomes) {
+ this.bookmarkIncomes.setBookmarkIncomes(bookmarkIncomes);
+ }
+
+ /**
+ * Resets the existing data of this {@code FinanceTracker} with {@code newData}.
+ */
+ public void resetData(ReadOnlyFinanceTracker newData) {
+ requireNonNull(newData);
+
+ setTransactions(newData.getTransactionList());
+ setBookmarkExpenses(newData.getBookmarkExpenseList());
+ setBookmarkIncomes(newData.getBookmarkIncomeList());
+ setMonthlyBudget(newData.getMonthlyBudget());
+ calculateBudgetInfo();
+ }
+
+ //// transaction-level operations
+
+ /**
+ * Adds a transaction to the finance tracker.
+ */
+ public void addTransaction(Transaction transaction) {
+ transactions.add(transaction);
+ }
+
+ /**
+ * Adds a bookmark expense to the finance tracker.
+ */
+ public void addBookmarkExpense(BookmarkExpense bookmarkExpense) {
+ bookmarkExpenses.add(bookmarkExpense);
+ }
+
+ /**
+ * Adds a bookmark income to the finance tracker.
+ */
+ public void addBookmarkIncome(BookmarkIncome bookmarkIncome) {
+ bookmarkIncomes.add(bookmarkIncome);
+ }
+
+ /**
+ * Replaces the given transaction {@code target} in the list with {@code editedTransaction}.
+ * {@code target} must exist in the finance tracker.
+ */
+ public void setTransaction(Transaction target, Transaction editedTransaction) {
+ requireNonNull(editedTransaction);
+
+ transactions.setTransaction(target, editedTransaction);
+ }
+
+ /**
+ * Replaces the given bookmark expense {@code target} in the list with {@code editedBookmarkExpense}.
+ * {@code target} must exist in the bookmark expense list.
+ */
+ public void setBookmarkExpense(BookmarkExpense target, BookmarkExpense editedBookmarkExpense) {
+ requireNonNull(editedBookmarkExpense);
+
+ bookmarkExpenses.setBookmarkExpense(target, editedBookmarkExpense);
+ }
+
+ /**
+ * Replaces the given bookmark income {@code target} in the list with {@code editedBookmarkIncome}.
+ * {@code target} must exist in the bookmark bookmark list.
+ */
+ public void setBookmarkIncome(BookmarkIncome target, BookmarkIncome editedBookmarkIncome) {
+ requireNonNull(editedBookmarkIncome);
+
+ bookmarkIncomes.setBookmarkIncome(target, editedBookmarkIncome);
+ }
+
+ /**
+ * Removes {@code key} from this {@code FinanceTracker}.
+ * {@code key} must exist in the finance tracker.
+ */
+ public void removeTransaction(Transaction key) {
+ transactions.remove(key);
+ }
+
+ /**
+ * Removes {@code key} from this {@code FinanceTracker}.
+ * {@code key} must exist in the finance tracker.
+ */
+ public void removeBookmarkExpense(BookmarkExpense key) {
+ bookmarkExpenses.remove(key);
+ }
+
+ /**
+ * Removes {@code key} from this {@code FinanceTracker}.
+ * {@code key} must exist in the finance tracker.
+ */
+ public void removeBookmarkIncome(BookmarkIncome key) {
+ bookmarkIncomes.remove(key);
+ }
+
+ //// budget-level operations
+
+ public void setExpenseLimit(Amount limit) {
+ monthlyBudget.setMonthlyExpenseLimit(limit);
+ }
+
+ public void setSavingsGoal(Amount goal) {
+ monthlyBudget.setMonthlySavingsGoal(goal);
+ }
+
+ public void setMonthlyBudget(MonthlyBudget budget) {
+ monthlyBudget.setMonthlyExpenseLimit(budget.getMonthlyExpenseLimit().get());
+ monthlyBudget.setMonthlySavingsGoal(budget.getMonthlySavingsGoal().get());
+ }
+
+ public void calculateBudgetInfo() {
+ monthlyBudget.calculateBudgetInfo(transactions, NUM_OF_MONTHS);
+ }
+
+ //// util methods
+
+ @Override
+ public String toString() {
+ return transactions.asUnmodifiableObservableList().size() + " transactions";
+ // TODO: refine later
+ }
+
+ @Override
+ public ObservableList getTransactionList() {
+ return transactions.asUnmodifiableObservableList();
+ }
+
+ @Override
+ public ObservableList getExpenseList() {
+ ObservableList expenses = FXCollections.observableArrayList();
+ transactions.forEach(t -> {
+ if (t instanceof Expense) {
+ expenses.add((Expense) t);
+ }
+ });
+ return FXCollections.unmodifiableObservableList(expenses);
+ }
+
+ @Override
+ public ObservableList getIncomeList() {
+ ObservableList incomes = FXCollections.observableArrayList();
+ transactions.forEach(t -> {
+ if (t instanceof Income) {
+ incomes.add((Income) t);
+ }
+ });
+ return FXCollections.unmodifiableObservableList(incomes);
+ }
+
+ @Override
+ public ObservableList getBookmarkExpenseList() {
+ return bookmarkExpenses.asUnmodifiableObservableList();
+ }
+
+ @Override
+ public ObservableList getBookmarkIncomeList() {
+ return bookmarkIncomes.asUnmodifiableObservableList();
+ }
+
+ public MonthlyBudget getMonthlyBudget() {
+ return monthlyBudget;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FinanceTracker // instanceof handles nulls
+ && transactions.equals(((FinanceTracker) other).transactions));
+ }
+
+ @Override
+ public int hashCode() {
+ return transactions.hashCode();
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/Model.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/Model.java
new file mode 100644
index 00000000000..a3520849f23
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/Model.java
@@ -0,0 +1,208 @@
+package ay2021s1_cs2103_w16_3.finesse.model;
+
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Predicate;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.GuiSettings;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkExpense;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkIncome;
+import ay2021s1_cs2103_w16_3.finesse.model.budget.MonthlyBudget;
+import ay2021s1_cs2103_w16_3.finesse.model.command.history.CommandHistory;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Expense;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Income;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Transaction;
+import javafx.collections.ObservableList;
+
+/**
+ * The API of the Model component.
+ */
+public interface Model {
+
+ /** {@code Predicate} that always evaluate to true. */
+ Predicate PREDICATE_SHOW_ALL_TRANSACTIONS = transaction -> true;
+
+ /** {@code Predicate} that evaluates to true if the transaction is an {@code Expense} */
+ Predicate PREDICATE_SHOW_ALL_EXPENSES = transaction -> transaction instanceof Expense;
+
+ /** {@code Predicate} that evaluates to true if the transaction is an {@code Income} */
+ Predicate PREDICATE_SHOW_ALL_INCOMES = transaction -> transaction instanceof Income;
+
+ /** {@code Predicate} that always evaluates to true. */
+ Predicate PREDICATE_SHOW_ALL_BOOKMARK_EXPENSES = bookmarkExpense -> true;
+
+ Predicate PREDICATE_SHOW_ALL_BOOKMARK_INCOMES = bookmarkIncome -> true;
+ /**
+ * Replaces user prefs data with the data in {@code userPrefs}.
+ */
+ void setUserPrefs(ReadOnlyUserPrefs userPrefs);
+
+ /**
+ * Returns the user prefs.
+ */
+ ReadOnlyUserPrefs getUserPrefs();
+
+ /**
+ * Returns the user prefs' GUI settings.
+ */
+ GuiSettings getGuiSettings();
+
+ /**
+ * Sets the user prefs' GUI settings.
+ */
+ void setGuiSettings(GuiSettings guiSettings);
+
+ /**
+ * Returns the user prefs' finance tracker file path.
+ */
+ Path getFinanceTrackerFilePath();
+
+ /**
+ * Sets the user prefs' finance tracker file path.
+ */
+ void setFinanceTrackerFilePath(Path financeTrackerFilePath);
+
+ /**
+ * Returns the command history.
+ */
+ CommandHistory getCommandHistory();
+
+ /**
+ * Replaces finance tracker data with the data in {@code financeTracker}.
+ */
+ void setFinanceTracker(ReadOnlyFinanceTracker financeTracker);
+
+ /** Returns the FinanceTracker */
+ ReadOnlyFinanceTracker getFinanceTracker();
+
+ /**
+ * Deletes the given transaction.
+ * The transaction must exist in the finance tracker.
+ */
+ void deleteTransaction(Transaction target);
+
+ /**
+ * Deletes the given bookmark expense.
+ * The bookmark expense must exist in the finance tracker.
+ */
+ void deleteBookmarkExpense(BookmarkExpense target);
+
+ /**
+ * Deletes the given bookmark income.
+ * The bookmark income must exist in the finance tracker.
+ */
+ void deleteBookmarkIncome(BookmarkIncome target);
+
+ /**
+ * Adds the given expense.
+ */
+ void addExpense(Expense expense);
+
+ /**
+ * Adds the given income.
+ */
+ void addIncome(Income income);
+
+ /**
+ * Adds the given bookmark expense.
+ */
+ void addBookmarkExpense(BookmarkExpense bookmarkExpense);
+
+ /**
+ * Adds the given bookmark income.
+ */
+ void addBookmarkIncome(BookmarkIncome bookmarkIncome);
+
+ /**
+ * Replaces the given transaction {@code target} with {@code editedTransaction}.
+ * {@code target} must exist in the finance tracker.
+ */
+ void setTransaction(Transaction target, Transaction editedTransaction);
+
+ /**
+ * Replaces the given bookmark expense {@code target} with {@code editedBookmarkExpense}.
+ * {@code target} must exist in the finance tracker.
+ */
+ void setBookmarkExpense(BookmarkExpense target, BookmarkExpense editedBookmarkExpense);
+
+ /**
+ * Replaces the given bookmark income {@code target} with {@code editedBookmarkIncome}.
+ * {@code target} must exist in the finance tracker.
+ */
+ void setBookmarkIncome(BookmarkIncome target, BookmarkIncome editedBookmarkIncome);
+
+ /** Returns the monthly budget. */
+ MonthlyBudget getMonthlyBudget();
+
+ /** Sets an expense limit. */
+ void setExpenseLimit(Amount limit);
+
+ /** Sets a savings goal. */
+ void setSavingsGoal(Amount goal);
+
+ void calculateBudgetInfo();
+
+ /** Returns an unmodifiable view of the filtered transaction list. */
+ ObservableList getFilteredTransactionList();
+
+ /** Returns an unmodifiable view of the filtered expense list. */
+ ObservableList getFilteredExpenseList();
+
+ /** Returns an unmodifiable view of the filtered income list. */
+ ObservableList getFilteredIncomeList();
+
+ /** Returns an unmodifiable view of the filtered bookmark expense list. */
+ ObservableList getFilteredBookmarkExpenseList();
+
+ /** Returns an unmodifiable view of the filtered bookmark income list. */
+ ObservableList getFilteredBookmarkIncomeList();
+
+ /**
+ * Updates the filter of the filtered transaction list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredTransactionList(Predicate predicate);
+
+ /**
+ * Updates the filter of the filtered transaction list to filter by the given list of {@code predicates}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredTransactionList(List> predicates);
+
+ /**
+ * Updates the filter of the filtered expense list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredExpenseList(Predicate predicate);
+
+ /**
+ * Updates the filter of the filtered expense list to filter by the given list of {@code predicates}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredExpenseList(List> predicates);
+
+ /**
+ * Updates the filter of the filtered income list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredIncomeList(Predicate predicate);
+
+ /**
+ * Updates the filter of the filtered income list to filter by the given list of {@code predicates}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredIncomeList(List> predicates);
+
+ /**
+ * Updates the filter of the filtered bookmark expense list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredBookmarkExpenseList(Predicate predicate);
+
+ /**
+ * Updates the filter of the filtered bookmark income list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredBookmarkIncomeList(Predicate predicate);
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/ModelManager.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/ModelManager.java
new file mode 100644
index 00000000000..efa4ec6d020
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/ModelManager.java
@@ -0,0 +1,366 @@
+package ay2021s1_cs2103_w16_3.finesse.model;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.util.CollectionUtil.requireAllNonNull;
+import static java.util.Objects.requireNonNull;
+
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import ay2021s1_cs2103_w16_3.finesse.commons.core.GuiSettings;
+import ay2021s1_cs2103_w16_3.finesse.commons.core.LogsCenter;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkExpense;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkIncome;
+import ay2021s1_cs2103_w16_3.finesse.model.budget.MonthlyBudget;
+import ay2021s1_cs2103_w16_3.finesse.model.command.history.CommandHistory;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Expense;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Income;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Transaction;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.collections.transformation.FilteredList;
+
+/**
+ * Represents the in-memory model of the finance tracker data.
+ */
+public class ModelManager implements Model {
+ private static final Logger logger = LogsCenter.getLogger(ModelManager.class);
+ private static final int COMMAND_HISTORY_SIZE = 50;
+
+ private final FinanceTracker financeTracker;
+ private final UserPrefs userPrefs;
+ private final CommandHistory commandHistory;
+ private final FilteredList filteredTransactions;
+ private final FilteredList filteredExpenses;
+ private final FilteredList filteredIncomes;
+ private final FilteredList filteredBookmarkExpenses;
+ private final FilteredList filteredBookmarkIncomes;
+ private final ObservableList castFilteredExpenses;
+ private final ObservableList castFilteredIncomes;
+ private final MonthlyBudget monthlyBudget;
+
+ /**
+ * Initializes a ModelManager with the given financeTracker and userPrefs.
+ */
+ public ModelManager(ReadOnlyFinanceTracker financeTracker, ReadOnlyUserPrefs userPrefs) {
+ super();
+ requireAllNonNull(financeTracker, userPrefs);
+
+ logger.fine("Initializing with finance tracker: " + financeTracker + " and user prefs " + userPrefs);
+
+ this.financeTracker = new FinanceTracker(financeTracker);
+ this.userPrefs = new UserPrefs(userPrefs);
+ commandHistory = new CommandHistory(COMMAND_HISTORY_SIZE);
+ filteredTransactions = new FilteredList<>(this.financeTracker.getTransactionList());
+ filteredExpenses = new FilteredList<>(this.financeTracker.getTransactionList(), PREDICATE_SHOW_ALL_EXPENSES);
+ filteredIncomes = new FilteredList<>(this.financeTracker.getTransactionList(), PREDICATE_SHOW_ALL_INCOMES);
+ filteredBookmarkExpenses = new FilteredList<>(this.financeTracker.getBookmarkExpenseList());
+ filteredBookmarkIncomes = new FilteredList<>(this.financeTracker.getBookmarkIncomeList());
+ castFilteredExpenses = FXCollections.observableArrayList(castFilteredList(filteredExpenses, Expense.class));
+ castFilteredIncomes = FXCollections.observableArrayList(castFilteredList(filteredIncomes, Income.class));
+ monthlyBudget = this.financeTracker.getMonthlyBudget();
+ }
+
+ public ModelManager() {
+ this(new FinanceTracker(), new UserPrefs());
+ }
+
+ //=========== UserPrefs ==================================================================================
+
+ @Override
+ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ requireNonNull(userPrefs);
+ this.userPrefs.resetData(userPrefs);
+ }
+
+ @Override
+ public ReadOnlyUserPrefs getUserPrefs() {
+ return userPrefs;
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ return userPrefs.getGuiSettings();
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ requireNonNull(guiSettings);
+ userPrefs.setGuiSettings(guiSettings);
+ }
+
+ @Override
+ public Path getFinanceTrackerFilePath() {
+ return userPrefs.getFinanceTrackerFilePath();
+ }
+
+ @Override
+ public void setFinanceTrackerFilePath(Path financeTrackerFilePath) {
+ requireNonNull(financeTrackerFilePath);
+ userPrefs.setFinanceTrackerFilePath(financeTrackerFilePath);
+ }
+
+ //=========== Command History ================================================================================
+
+ @Override
+ public CommandHistory getCommandHistory() {
+ return commandHistory;
+ }
+
+
+ //=========== FinanceTracker ================================================================================
+
+ @Override
+ public void setFinanceTracker(ReadOnlyFinanceTracker financeTracker) {
+ this.financeTracker.resetData(financeTracker);
+ refreshTransactionLists();
+ }
+
+ @Override
+ public ReadOnlyFinanceTracker getFinanceTracker() {
+ return financeTracker;
+ }
+
+ @Override
+ public void deleteTransaction(Transaction target) {
+ financeTracker.removeTransaction(target);
+ refreshTransactionLists();
+ }
+
+ @Override
+ public void addExpense(Expense expense) {
+ financeTracker.addTransaction(expense);
+ updateFilteredExpenseList(PREDICATE_SHOW_ALL_TRANSACTIONS);
+ }
+
+ @Override
+ public void addIncome(Income income) {
+ financeTracker.addTransaction(income);
+ updateFilteredIncomeList(PREDICATE_SHOW_ALL_TRANSACTIONS);
+ }
+
+ @Override
+ public void setTransaction(Transaction target, Transaction editedTransaction) {
+ requireAllNonNull(target, editedTransaction);
+
+ financeTracker.setTransaction(target, editedTransaction);
+ refreshTransactionLists();
+ }
+
+ //=========== Bookmark Transaction ================================================================================
+
+ @Override
+ public void addBookmarkExpense(BookmarkExpense bookmarkExpense) {
+ financeTracker.addBookmarkExpense(bookmarkExpense);
+ }
+
+ @Override
+ public void addBookmarkIncome(BookmarkIncome bookmarkIncome) {
+ financeTracker.addBookmarkIncome(bookmarkIncome);
+ }
+
+ @Override
+ public void deleteBookmarkExpense(BookmarkExpense bookmarkExpense) {
+ financeTracker.removeBookmarkExpense(bookmarkExpense);
+ }
+
+ @Override
+ public void deleteBookmarkIncome(BookmarkIncome bookmarkIncome) {
+ financeTracker.removeBookmarkIncome(bookmarkIncome);
+ }
+
+ @Override
+ public void setBookmarkExpense(BookmarkExpense target, BookmarkExpense editedBookmarkExpense) {
+ requireAllNonNull(target, editedBookmarkExpense);
+
+ financeTracker.setBookmarkExpense(target, editedBookmarkExpense);
+ }
+
+ @Override
+ public void setBookmarkIncome(BookmarkIncome target, BookmarkIncome editedBookmarkIncome) {
+ requireAllNonNull(target, editedBookmarkIncome);
+
+ financeTracker.setBookmarkIncome(target, editedBookmarkIncome);
+ }
+
+ @Override
+ public void updateFilteredBookmarkExpenseList(Predicate predicate) {
+ requireNonNull(predicate);
+
+ filteredBookmarkExpenses.setPredicate(predicate);
+ }
+
+ @Override
+ public void updateFilteredBookmarkIncomeList(Predicate predicate) {
+ requireNonNull(predicate);
+
+ filteredBookmarkIncomes.setPredicate(predicate);
+ }
+
+ //=========== Budget ===========================================================================================
+ @Override
+ public MonthlyBudget getMonthlyBudget() {
+ return monthlyBudget;
+ }
+
+ @Override
+ public void setExpenseLimit(Amount limit) {
+ monthlyBudget.setMonthlyExpenseLimit(limit);
+ calculateBudgetInfo();
+ }
+
+ @Override
+ public void setSavingsGoal(Amount goal) {
+ monthlyBudget.setMonthlySavingsGoal(goal);
+ calculateBudgetInfo();
+ }
+
+ @Override
+ public void calculateBudgetInfo() {
+ financeTracker.calculateBudgetInfo();
+ }
+
+ //=========== Filtered Transaction List Accessors =============================================================
+
+ /**
+ * Returns an unmodifiable view of the list of {@code Transaction} backed by the internal transaction list of
+ * {@code versionedFinanceTracker}.
+ */
+ @Override
+ public ObservableList getFilteredTransactionList() {
+ return filteredTransactions;
+ }
+
+ /**
+ * Returns an unmodifiable view of the list of {@code Expense} backed by the internal expense list of
+ * {@code versionedFinanceTracker}.
+ */
+ @Override
+ public ObservableList getFilteredExpenseList() {
+ return FXCollections.unmodifiableObservableList(castFilteredExpenses);
+ }
+
+ /**
+ * Returns an unmodifiable view of the list of {@code Income} backed by the internal income list of
+ * {@code versionedFinanceTracker}.
+ */
+ @Override
+ public ObservableList getFilteredIncomeList() {
+ return FXCollections.unmodifiableObservableList(castFilteredIncomes);
+ }
+
+ /**
+ * Returns an unmodifiable view of the list of {@code BookmarkExpense} backed by the internal bookmark expense
+ * list of {@code versionedFinanceTracker}.
+ */
+ @Override
+ public ObservableList getFilteredBookmarkExpenseList() {
+ return filteredBookmarkExpenses;
+ }
+
+ /**
+ * Returns an unmodifiable view of the list of {@code BookmarkIncome} backed by the internal bookmark income
+ * list of {@code versionedFinanceTracker}.
+ */
+ @Override
+ public ObservableList getFilteredBookmarkIncomeList() {
+ return filteredBookmarkIncomes;
+ }
+
+ @Override
+ public void updateFilteredTransactionList(Predicate predicate) {
+ requireNonNull(predicate);
+ filteredTransactions.setPredicate(predicate);
+ }
+
+ @Override
+ public void updateFilteredTransactionList(List> predicates) {
+ requireNonNull(predicates);
+ Predicate combinedPredicate = predicates.stream()
+ .reduce(PREDICATE_SHOW_ALL_TRANSACTIONS, Predicate::and);
+ updateFilteredTransactionList(combinedPredicate);
+ }
+
+ @Override
+ public void updateFilteredExpenseList(Predicate predicate) {
+ requireNonNull(predicate);
+ filteredExpenses.setPredicate(t -> predicate.test(t) && PREDICATE_SHOW_ALL_EXPENSES.test(t));
+ refreshTransactionLists();
+ }
+
+ @Override
+ public void updateFilteredExpenseList(List> predicates) {
+ requireNonNull(predicates);
+ Predicate combinedPredicate = predicates.stream()
+ .reduce(PREDICATE_SHOW_ALL_EXPENSES, Predicate::and);
+ updateFilteredExpenseList(combinedPredicate);
+ }
+
+ @Override
+ public void updateFilteredIncomeList(Predicate predicate) {
+ requireNonNull(predicate);
+ filteredIncomes.setPredicate(t -> predicate.test(t) && PREDICATE_SHOW_ALL_INCOMES.test(t));
+ refreshTransactionLists();
+ }
+
+ @Override
+ public void updateFilteredIncomeList(List> predicates) {
+ requireNonNull(predicates);
+ Predicate combinedPredicate = predicates.stream()
+ .reduce(PREDICATE_SHOW_ALL_INCOMES, Predicate::and);
+ updateFilteredIncomeList(combinedPredicate);
+ }
+
+ /**
+ * Casts a FilteredList into a List of the filtered subtype.
+ * Precondition: The given FilteredList must be either the expense list or income list.
+ * This guarantees that the elements of the FilteredList can successfully be cast to the target type.
+ *
+ * @param list The FilteredList to be cast.
+ * @param toCast The class of the target type to be cast. Required as casting to generic types is not allowed.
+ * @param The type of transaction. Can be either Expense or Income, but not Transaction itself.
+ * @return A List whose elements are equal to the current state of the FilteredList.
+ */
+ private List castFilteredList(FilteredList list, Class toCast) {
+ assert list == filteredExpenses || list == filteredIncomes
+ : "Casting can only be performed on expense or income list";
+ assert toCast != Transaction.class : "Casting can only be to Expense or Income, not Transaction";
+ return list.stream().map(toCast::cast).collect(Collectors.toUnmodifiableList());
+ }
+
+ /**
+ * Refreshes the typecasted ObservableLists used by other components.
+ * To be called anytime the expense or income list updates.
+ */
+ private void refreshTransactionLists() {
+ castFilteredExpenses.clear();
+ castFilteredExpenses.addAll(castFilteredList(filteredExpenses, Expense.class));
+ castFilteredIncomes.clear();
+ castFilteredIncomes.addAll(castFilteredList(filteredIncomes, Income.class));
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // short circuit if same object
+ if (obj == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(obj instanceof ModelManager)) {
+ return false;
+ }
+
+ // state check
+ ModelManager other = (ModelManager) obj;
+ return financeTracker.equals(other.financeTracker)
+ && userPrefs.equals(other.userPrefs)
+ && commandHistory.equals(other.commandHistory)
+ && filteredTransactions.equals(other.filteredTransactions)
+ && filteredExpenses.equals(other.filteredExpenses)
+ && filteredIncomes.equals(other.filteredIncomes);
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/ReadOnlyFinanceTracker.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/ReadOnlyFinanceTracker.java
new file mode 100644
index 00000000000..cbb292f7dec
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/ReadOnlyFinanceTracker.java
@@ -0,0 +1,46 @@
+package ay2021s1_cs2103_w16_3.finesse.model;
+
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkExpense;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.BookmarkIncome;
+import ay2021s1_cs2103_w16_3.finesse.model.budget.MonthlyBudget;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Expense;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Income;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Transaction;
+import javafx.collections.ObservableList;
+
+/**
+ * Unmodifiable view of a finance tracker
+ */
+public interface ReadOnlyFinanceTracker {
+
+ /**
+ * Returns an unmodifiable view of the transactions list.
+ */
+ ObservableList getTransactionList();
+
+ /**
+ * Returns an unmodifiable view of the transactions list.
+ */
+ ObservableList getExpenseList();
+
+ /**
+ * Returns an unmodifiable view of the transactions list.
+ */
+ ObservableList getIncomeList();
+
+ /**
+ * Returns an unmodifiable view of the bookmark expense list.
+ */
+ ObservableList getBookmarkExpenseList();
+
+ /**
+ * Returns an unmodifiable view of the bookmark income list.
+ */
+ ObservableList getBookmarkIncomeList();
+
+ /**
+ * Returns the monthly budget.
+ */
+ MonthlyBudget getMonthlyBudget();
+
+}
diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/ReadOnlyUserPrefs.java
similarity index 50%
rename from src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/model/ReadOnlyUserPrefs.java
index befd58a4c73..ab2d5b3c42e 100644
--- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/ReadOnlyUserPrefs.java
@@ -1,8 +1,8 @@
-package seedu.address.model;
+package ay2021s1_cs2103_w16_3.finesse.model;
import java.nio.file.Path;
-import seedu.address.commons.core.GuiSettings;
+import ay2021s1_cs2103_w16_3.finesse.commons.core.GuiSettings;
/**
* Unmodifiable view of user prefs.
@@ -11,6 +11,6 @@ public interface ReadOnlyUserPrefs {
GuiSettings getGuiSettings();
- Path getAddressBookFilePath();
+ Path getFinanceTrackerFilePath();
}
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/UserPrefs.java
similarity index 68%
rename from src/main/java/seedu/address/model/UserPrefs.java
rename to src/main/java/ay2021s1_cs2103_w16_3/finesse/model/UserPrefs.java
index 25a5fd6eab9..61b247f013d 100644
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/UserPrefs.java
@@ -1,4 +1,4 @@
-package seedu.address.model;
+package ay2021s1_cs2103_w16_3.finesse.model;
import static java.util.Objects.requireNonNull;
@@ -6,7 +6,7 @@
import java.nio.file.Paths;
import java.util.Objects;
-import seedu.address.commons.core.GuiSettings;
+import ay2021s1_cs2103_w16_3.finesse.commons.core.GuiSettings;
/**
* Represents User's preferences.
@@ -14,7 +14,7 @@
public class UserPrefs implements ReadOnlyUserPrefs {
private GuiSettings guiSettings = new GuiSettings();
- private Path addressBookFilePath = Paths.get("data" , "addressbook.json");
+ private Path financeTrackerFilePath = Paths.get("data" , "fine$$e.json");
/**
* Creates a {@code UserPrefs} with default values.
@@ -35,7 +35,7 @@ public UserPrefs(ReadOnlyUserPrefs userPrefs) {
public void resetData(ReadOnlyUserPrefs newUserPrefs) {
requireNonNull(newUserPrefs);
setGuiSettings(newUserPrefs.getGuiSettings());
- setAddressBookFilePath(newUserPrefs.getAddressBookFilePath());
+ setFinanceTrackerFilePath(newUserPrefs.getFinanceTrackerFilePath());
}
public GuiSettings getGuiSettings() {
@@ -47,13 +47,13 @@ public void setGuiSettings(GuiSettings guiSettings) {
this.guiSettings = guiSettings;
}
- public Path getAddressBookFilePath() {
- return addressBookFilePath;
+ public Path getFinanceTrackerFilePath() {
+ return financeTrackerFilePath;
}
- public void setAddressBookFilePath(Path addressBookFilePath) {
- requireNonNull(addressBookFilePath);
- this.addressBookFilePath = addressBookFilePath;
+ public void setFinanceTrackerFilePath(Path financeTrackerFilePath) {
+ requireNonNull(financeTrackerFilePath);
+ this.financeTrackerFilePath = financeTrackerFilePath;
}
@Override
@@ -68,19 +68,19 @@ public boolean equals(Object other) {
UserPrefs o = (UserPrefs) other;
return guiSettings.equals(o.guiSettings)
- && addressBookFilePath.equals(o.addressBookFilePath);
+ && financeTrackerFilePath.equals(o.financeTrackerFilePath);
}
@Override
public int hashCode() {
- return Objects.hash(guiSettings, addressBookFilePath);
+ return Objects.hash(guiSettings, financeTrackerFilePath);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Gui Settings : " + guiSettings);
- sb.append("\nLocal data file location : " + addressBookFilePath);
+ sb.append("\nLocal data file location : " + financeTrackerFilePath);
return sb.toString();
}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/BookmarkExpense.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/BookmarkExpense.java
new file mode 100644
index 00000000000..5ba93da497a
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/BookmarkExpense.java
@@ -0,0 +1,52 @@
+package ay2021s1_cs2103_w16_3.finesse.model.bookmark;
+
+import java.util.Set;
+
+import ay2021s1_cs2103_w16_3.finesse.model.category.Category;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Date;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Expense;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Title;
+
+public class BookmarkExpense extends BookmarkTransaction {
+
+ public BookmarkExpense(Title title, Amount amount, Set categories) {
+ super(title, amount, categories);
+ }
+
+ /**
+ * Returns true if the titles of both bookmark expenses are the same.
+ */
+ public boolean hasSameTitle(Object other) {
+ return other instanceof BookmarkExpense && getTitle().equals(((BookmarkExpense) other).getTitle());
+ }
+
+ @Override
+ public Expense convert(Date expenseDate) {
+ Title title = getTitle();
+ Amount amount = getAmount();
+ Set categories = getCategories();
+
+ return new Expense(title, amount, expenseDate, categories);
+ }
+
+ /**
+ * Returns true if both bookmark expenses have the same identity and data fields.
+ * This defines a stronger notion of equality between two bookmark expenses.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof BookmarkExpense)) {
+ return false;
+ }
+
+ BookmarkExpense otherBookmarkExpense = (BookmarkExpense) other;
+ return getTitle().equals(otherBookmarkExpense.getTitle())
+ && getAmount().equals(otherBookmarkExpense.getAmount())
+ && getCategories().equals(otherBookmarkExpense.getCategories());
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/BookmarkExpenseList.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/BookmarkExpenseList.java
new file mode 100644
index 00000000000..339af066deb
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/BookmarkExpenseList.java
@@ -0,0 +1,120 @@
+package ay2021s1_cs2103_w16_3.finesse.model.bookmark;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.util.CollectionUtil.requireAllNonNull;
+import static java.util.Objects.requireNonNull;
+
+import java.util.Iterator;
+import java.util.List;
+
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.exceptions.BookmarkTransactionNotFoundException;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.exceptions.DuplicateBookmarkTransactionException;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+/**
+ * A list of bookmark expenses that does not allow nulls.
+ * The removal of a bookmark expense uses BookmarkExpense#equals(Object) so as to ensure that the bookmark expense
+ * with exactly the same fields will be removed.
+ *
+ * Supports a minimal set of list operations.
+ */
+public class BookmarkExpenseList implements Iterable {
+ public static final String MESSAGE_OPERATION_WILL_RESULT_IN_DUPLICATE_BOOKMARK_EXPENSE =
+ "Operation will result in duplicate bookmark expense";
+
+ private final ObservableList internalBookmarkExpenseList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableBookmarkExpenseList =
+ FXCollections.unmodifiableObservableList(internalBookmarkExpenseList);
+
+ /**
+ * Adds a bookmark expense to the list.
+ */
+ public void add(BookmarkExpense toAdd) {
+ requireNonNull(toAdd);
+ if (contains(toAdd)) {
+ throw new DuplicateBookmarkTransactionException(
+ MESSAGE_OPERATION_WILL_RESULT_IN_DUPLICATE_BOOKMARK_EXPENSE);
+ }
+ internalBookmarkExpenseList.add(toAdd);
+ }
+
+ /**
+ * Returns true if the bookmark expense list contains a bookmark expense with the same title as the given argument.
+ */
+ public boolean contains(BookmarkExpense toCheck) {
+ requireNonNull(toCheck);
+ return internalBookmarkExpenseList.stream().anyMatch(toCheck::hasSameTitle);
+ }
+
+ /**
+ * Replaces the bookmark expense {@code target} in the list with {@code editedBookmarkExpense}.
+ * {@code target} must exist in the list.
+ * The bookmark expense identity of {@code editedBookmarkExpense} must not be the same as another existing
+ * bookmark expense in the list.
+ */
+ public void setBookmarkExpense(BookmarkExpense target, BookmarkExpense editedBookmarkExpense) {
+ requireAllNonNull(target, editedBookmarkExpense);
+
+ int index = internalBookmarkExpenseList.indexOf(target);
+ if (index == -1) {
+ throw new BookmarkTransactionNotFoundException();
+ }
+
+ if (contains(editedBookmarkExpense) && !(target.getTitle().equals(editedBookmarkExpense.getTitle()))) {
+ throw new DuplicateBookmarkTransactionException(
+ MESSAGE_OPERATION_WILL_RESULT_IN_DUPLICATE_BOOKMARK_EXPENSE);
+ }
+
+ internalBookmarkExpenseList.set(index, editedBookmarkExpense);
+ }
+
+ /**
+ * Removes the equivalent bookmark expense from the list.
+ * The bookmark expense must exist in the list.
+ */
+ public void remove(BookmarkExpense toRemove) {
+ requireNonNull(toRemove);
+ if (!(internalBookmarkExpenseList.remove(toRemove))) {
+ throw new BookmarkTransactionNotFoundException();
+ }
+ }
+
+ /**
+ * Returns the backing list as an unmodifiable {@code ObservableList}.
+ */
+ public ObservableList asUnmodifiableObservableList() {
+ return internalUnmodifiableBookmarkExpenseList;
+ }
+
+ /**
+ * Replaces the contents of this list with {@code bookmarkExpenses}.
+ */
+ public void setBookmarkExpenses(List bookmarkExpenses) {
+ requireAllNonNull(bookmarkExpenses);
+
+ internalBookmarkExpenseList.setAll(bookmarkExpenses);
+ }
+
+ public void setBookmarkExpenses(BookmarkExpenseList replacement) {
+ requireNonNull(replacement);
+ internalBookmarkExpenseList.setAll(replacement.internalBookmarkExpenseList);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof BookmarkExpenseList // instanceof handles nulls
+ && internalBookmarkExpenseList.equals(((BookmarkExpenseList) other).internalBookmarkExpenseList));
+ }
+
+ @Override
+ public int hashCode() {
+ return internalBookmarkExpenseList.hashCode();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return internalBookmarkExpenseList.iterator();
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/BookmarkIncome.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/BookmarkIncome.java
new file mode 100644
index 00000000000..031eacfd693
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/BookmarkIncome.java
@@ -0,0 +1,51 @@
+package ay2021s1_cs2103_w16_3.finesse.model.bookmark;
+
+import java.util.Set;
+
+import ay2021s1_cs2103_w16_3.finesse.model.category.Category;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Date;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Income;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Title;
+
+public class BookmarkIncome extends BookmarkTransaction {
+
+ public BookmarkIncome(Title title, Amount amount, Set categories) {
+ super(title, amount, categories);
+ }
+
+ /**
+ * Returns true if the titles of both bookmark incomes are the same.
+ */
+ public boolean hasSameTitle(Object other) {
+ return other instanceof BookmarkIncome && getTitle().equals(((BookmarkIncome) other).getTitle());
+ }
+
+ @Override
+ public Income convert(Date date) {
+ Title title = getTitle();
+ Amount amount = getAmount();
+ Set categories = getCategories();
+
+ return new Income(title, amount, date, categories);
+ }
+
+ /**
+ * Returns true if both bookmark incomes have the same identity and data fields.
+ * This defines a stronger notion of equality between two bookmark incomes.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof BookmarkIncome)) {
+ return false;
+ }
+ BookmarkIncome otherBookmarkIncome = (BookmarkIncome) other;
+ return getTitle().equals(otherBookmarkIncome.getTitle())
+ && getAmount().equals(otherBookmarkIncome.getAmount())
+ && getCategories().equals(otherBookmarkIncome.getCategories());
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/BookmarkIncomeList.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/BookmarkIncomeList.java
new file mode 100644
index 00000000000..87390c4a5c9
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/BookmarkIncomeList.java
@@ -0,0 +1,120 @@
+package ay2021s1_cs2103_w16_3.finesse.model.bookmark;
+
+import static ay2021s1_cs2103_w16_3.finesse.commons.util.CollectionUtil.requireAllNonNull;
+import static java.util.Objects.requireNonNull;
+
+import java.util.Iterator;
+import java.util.List;
+
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.exceptions.BookmarkTransactionNotFoundException;
+import ay2021s1_cs2103_w16_3.finesse.model.bookmark.exceptions.DuplicateBookmarkTransactionException;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+/**
+ * A list of bookmark incomes that does not allow nulls.
+ * The removal of a bookmark income uses BookmarkIncome#equals(Object) so as to ensure that the bookmark income
+ * with exactly the same fields will be removed.
+ *
+ * Supports a minimal set of list operations.
+ */
+public class BookmarkIncomeList implements Iterable {
+ public static final String MESSAGE_OPERATION_WOULD_RESULT_IN_DUPLICATE_BOOKMARK_INCOME =
+ "Operation would result in duplicate bookmark income";
+
+ private final ObservableList internalBookmarkIncomeList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableBookmarkIncomeList =
+ FXCollections.unmodifiableObservableList(internalBookmarkIncomeList);
+
+ /**
+ * Adds a bookmark income to the list.
+ */
+ public void add(BookmarkIncome toAdd) {
+ requireNonNull(toAdd);
+ if (contains(toAdd)) {
+ throw new DuplicateBookmarkTransactionException(
+ MESSAGE_OPERATION_WOULD_RESULT_IN_DUPLICATE_BOOKMARK_INCOME);
+ }
+ internalBookmarkIncomeList.add(toAdd);
+ }
+
+ /**
+ * Returns true if the bookmark income list contains a bookmark income with the same title as the given argument.
+ */
+ public boolean contains(BookmarkIncome toCheck) {
+ requireNonNull(toCheck);
+ return internalBookmarkIncomeList.stream().anyMatch(toCheck::hasSameTitle);
+ }
+
+ /**
+ * Replaces the bookmark income {@code target} in the list with {@code editedBookmarkIncome}.
+ * {@code target} must exist in the list.
+ * The bookmark income identity of {@code editedBookmarkIncome} must not be the same as another existing
+ * bookmark income in the list.
+ */
+ public void setBookmarkIncome(BookmarkIncome target, BookmarkIncome editedBookmarkIncome) {
+ requireAllNonNull(target, editedBookmarkIncome);
+
+ int index = internalBookmarkIncomeList.indexOf(target);
+ if (index == -1) {
+ throw new BookmarkTransactionNotFoundException();
+ }
+
+ if (contains(editedBookmarkIncome) && !(target.getTitle().equals(editedBookmarkIncome.getTitle()))) {
+ throw new DuplicateBookmarkTransactionException(
+ MESSAGE_OPERATION_WOULD_RESULT_IN_DUPLICATE_BOOKMARK_INCOME);
+ }
+
+ internalBookmarkIncomeList.set(index, editedBookmarkIncome);
+ }
+
+ /**
+ * Returns the backing list as an unmodifiable {@code ObservableList}.
+ */
+ public ObservableList asUnmodifiableObservableList() {
+ return internalUnmodifiableBookmarkIncomeList;
+ }
+
+ /**
+ * Removes the equivalent bookmark income from the list.
+ * The bookmark income must exist in the list.
+ */
+ public void remove(BookmarkIncome toRemove) {
+ requireNonNull(toRemove);
+ if (!(internalBookmarkIncomeList.remove(toRemove))) {
+ throw new BookmarkTransactionNotFoundException();
+ }
+ }
+
+ /**
+ * Replaces the contents of this list with {@code bookmarkIncomes}.
+ */
+ public void setBookmarkIncomes(List bookmarkIncomes) {
+ requireAllNonNull(bookmarkIncomes);
+
+ internalBookmarkIncomeList.setAll(bookmarkIncomes);
+ }
+
+ public void setBookmarkIncomes(BookmarkIncomeList replacement) {
+ requireNonNull(replacement);
+ internalBookmarkIncomeList.setAll(replacement.internalBookmarkIncomeList);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof BookmarkIncomeList)
+ && internalBookmarkIncomeList.equals(((BookmarkIncomeList) other).internalBookmarkIncomeList);
+ }
+
+ @Override
+ public int hashCode() {
+ return internalBookmarkIncomeList.hashCode();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return internalBookmarkIncomeList.iterator();
+ }
+
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/BookmarkTransaction.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/BookmarkTransaction.java
new file mode 100644
index 00000000000..86f0c34b011
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/BookmarkTransaction.java
@@ -0,0 +1,60 @@
+package ay2021s1_cs2103_w16_3.finesse.model.bookmark;
+
+import java.util.Collections;
+import java.util.Set;
+
+import ay2021s1_cs2103_w16_3.finesse.model.category.Category;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Date;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Title;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Transaction;
+
+/**
+ * Represents a Bookmark Transaction in the finance tracker.
+ * Guarantees: details are present and not null, field values are validated, immutable.
+ */
+public abstract class BookmarkTransaction {
+
+ public static final String MESSAGE_TITLE_CONSTRAINTS = "Bookmark transactions should only contain one title";
+ public static final String MESSAGE_AMOUNT_CONSTRAINTS = "Bookmark transactions should only contain one amount";
+ public static final String MESSAGE_CANNOT_CONTAIN_DATE = "Bookmark transactions should not contain any date";
+
+ private final Title title;
+ private final Amount amount;
+ private final Set categories;
+
+ protected BookmarkTransaction(Title title, Amount amount, Set categories) {
+ this.title = title;
+ this.amount = amount;
+ this.categories = categories;
+ }
+
+ public Title getTitle() {
+ return this.title;
+ }
+
+ public Amount getAmount() {
+ return this.amount;
+ }
+
+ /**
+ * Returns an immutable category set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ */
+ public Set getCategories() {
+ return Collections.unmodifiableSet(categories);
+ }
+
+ public abstract T convert(Date date);
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(getTitle())
+ .append(" Amount: ")
+ .append(getAmount())
+ .append(" Categories: ");
+ getCategories().forEach(builder::append);
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/exceptions/BookmarkTransactionNotFoundException.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/exceptions/BookmarkTransactionNotFoundException.java
new file mode 100644
index 00000000000..f4ad5250bad
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/exceptions/BookmarkTransactionNotFoundException.java
@@ -0,0 +1,6 @@
+package ay2021s1_cs2103_w16_3.finesse.model.bookmark.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified input
+ */
+public class BookmarkTransactionNotFoundException extends RuntimeException {}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/exceptions/DuplicateBookmarkTransactionException.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/exceptions/DuplicateBookmarkTransactionException.java
new file mode 100644
index 00000000000..6161f207f6e
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/bookmark/exceptions/DuplicateBookmarkTransactionException.java
@@ -0,0 +1,10 @@
+package ay2021s1_cs2103_w16_3.finesse.model.bookmark.exceptions;
+
+/**
+ * Signals that the following operation will result in a duplicate bookmark transaction.
+ */
+public class DuplicateBookmarkTransactionException extends RuntimeException {
+ public DuplicateBookmarkTransactionException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/budget/MonthlyBudget.java b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/budget/MonthlyBudget.java
new file mode 100644
index 00000000000..8073974970d
--- /dev/null
+++ b/src/main/java/ay2021s1_cs2103_w16_3/finesse/model/budget/MonthlyBudget.java
@@ -0,0 +1,237 @@
+package ay2021s1_cs2103_w16_3.finesse.model.budget;
+
+import java.text.DateFormatSymbols;
+import java.time.YearMonth;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.StreamSupport;
+
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Amount.CalculatedAmount;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Expense;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Income;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.Transaction;
+import ay2021s1_cs2103_w16_3.finesse.model.transaction.TransactionList;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+/**
+ * Represents the monthly budget in the finance tracker.
+ */
+public class MonthlyBudget {
+ private static final String[] MONTHS = new DateFormatSymbols().getMonths();
+ private static final int NUM_OF_MONTHS = 12;
+
+ private ObjectProperty monthlyExpenseLimit;
+ private ObjectProperty monthlySavingsGoal;
+ private ObjectProperty remainingBudget;
+ private ObjectProperty currentSavings;
+ private ObservableList monthlyExpenses;
+ private ObservableList monthlyIncomes;
+ private ObservableList monthlySavings;
+ private ObservableList months;
+
+ @FunctionalInterface
+ public interface ChangeListener extends Runnable {
+
+ }
+
+ private Collection listeners;
+
+ /**
+ * Creates a {@code MonthlyBudget} with an expense limit and savings goal of $0.
+ */
+ public MonthlyBudget() {
+ monthlyExpenseLimit = new SimpleObjectProperty<>(Amount.ZERO_AMOUNT);
+ monthlySavingsGoal = new SimpleObjectProperty<>(Amount.ZERO_AMOUNT);
+ remainingBudget = new SimpleObjectProperty<>(new CalculatedAmount());
+ currentSavings = new SimpleObjectProperty<>(new CalculatedAmount());
+ monthlyExpenses = FXCollections.observableArrayList();
+ monthlyIncomes = FXCollections.observableArrayList();
+ monthlySavings = FXCollections.observableArrayList();
+ months = FXCollections.observableArrayList();
+ listeners = new ArrayList<>();
+ }
+
+ public ObjectProperty getMonthlyExpenseLimit() {
+ return monthlyExpenseLimit;
+ }
+
+ public void setMonthlyExpenseLimit(Amount limit) {
+ monthlyExpenseLimit.setValue(limit);
+ }
+
+ public ObjectProperty getMonthlySavingsGoal() {
+ return monthlySavingsGoal;
+ }
+
+ public void setMonthlySavingsGoal(Amount goal) {
+ monthlySavingsGoal.setValue(goal);
+ }
+
+ public ObjectProperty getRemainingBudget() {
+ return remainingBudget;
+ }
+
+ public void setRemainingBudget(CalculatedAmount budget) {
+ remainingBudget.setValue(budget);
+ }
+
+ public ObjectProperty getCurrentSavings() {
+ return currentSavings;
+ }
+
+ public void setCurrentSavings(CalculatedAmount savings) {
+ currentSavings.setValue(savings);
+ }
+
+ public ObservableList getMonthlyExpenses() {
+ return FXCollections.unmodifiableObservableList(monthlyExpenses);
+ }
+
+ public ObservableList getMonthlyIncomes() {
+ return FXCollections.unmodifiableObservableList(monthlyIncomes);
+ }
+
+ public ObservableList getMonthlySavings() {
+ return FXCollections.unmodifiableObservableList(monthlySavings);
+ }
+
+ public ObservableList getMonths() {
+ return FXCollections.unmodifiableObservableList(months);
+ }
+
+ public void addChangeListener(ChangeListener listener) {
+ listeners.add(listener);
+ }
+
+ private void callChangeListeners() {
+ listeners.forEach(ChangeListener::run);
+ }
+
+ /**
+ * Calculates all information related to the user's savings.
+ * These are:
+ * 1. Expenses for the past number of months specified by the user
+ * 2. Incomes for the past number of months specified by user
+ * 3. Savings for the past number of months specified by user
+ * 4. The months to be displayed according to the number of months specified by user
+ *
+ * @param transactions The current list of transactions.
+ * @param numOfMonths The number of months of data the user wishes to display.
+ */
+ public void calculateBudgetInfo(TransactionList transactions, int numOfMonths) {
+ this.monthlyExpenses.clear();
+ this.monthlyIncomes.clear();
+ this.months.clear();
+
+ YearMonth today = YearMonth.now();
+ int thisMonthValue = today.getMonthValue();
+
+ // function to get the month the transaction was dated, relative to today
+ Function monthsBeforeToday = transaction -> (int) ChronoUnit.MONTHS.between(
+ YearMonth.from(transaction.getDateValue()), today);
+
+ // filter transactions that are outside the desired range, and split according to class (expense vs income)
+ Map