diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
new file mode 100644
index 0000000000..47972eb0ac
--- /dev/null
+++ b/.github/workflows/gradle.yml
@@ -0,0 +1,50 @@
+name: Java CI
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ platform: [ubuntu-latest, macos-latest, windows-latest]
+ runs-on: ${{ matrix.platform }}
+
+ 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: 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: Build and check with Gradle
+ run: ./gradlew check
+
+ # - name: Perform IO redirection test (*NIX)
+ # if: runner.os == 'Linux'
+ # working-directory: ${{ github.workspace }}/text-ui-test
+ # run: ./runtest.sh
+
+ # - name: Perform IO redirection test (MacOS)
+ # if: always() && runner.os == 'macOS'
+ # working-directory: ${{ github.workspace }}/text-ui-test
+ # run: ./runtest.sh
+
+ # - name: Perform IO redirection test (Windows)
+ # if: always() && runner.os == 'Windows'
+ # working-directory: ${{ github.workspace }}/text-ui-test
+ # shell: cmd
+ # run: runtest.bat
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000..a882e57307
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "java.configuration.updateBuildConfiguration": "automatic",
+ "java.checkstyle.configuration": "${workspaceFolder}\\config\\checkstyle\\checkstyle.xml"
+}
diff --git a/README.md b/README.md
index 8715d4d915..a1dd614ac8 100644
--- a/README.md
+++ b/README.md
@@ -13,12 +13,4 @@ Prerequisites: JDK 11, update Intellij to the most recent version.
1. If there are any further prompts, accept the defaults.
1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
In the same dialog, set the **Project language level** field to the `SDK default` option.
-3. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output:
- ```
- Hello from
- ____ _
- | _ \ _ _| | _____
- | | | | | | | |/ / _ \
- | |_| | |_| | < __/
- |____/ \__,_|_|\_\___|
- ```
+1. After that, locate the `src/main/java/Launcher.java` file, right-click it, and choose `Run Launcher.main()` (if the code editor is showing compile errors, try restarting the IDE).
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..507751640a
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,63 @@
+plugins {
+ id 'java'
+ id 'application'
+ id 'checkstyle'
+ id 'org.openjfx.javafxplugin' version '0.0.10'
+ id 'com.github.johnrengelman.shadow' version '5.1.0'
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation('org.junit.platform:junit-platform-launcher:1.5.2')
+ testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.6.0'
+ testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.6.0'
+}
+
+jar {
+ manifest {
+ attributes(
+ 'Main-Class': 'duke.Launcher'
+ )
+ }
+}
+
+javafx {
+ version = "11"
+ modules = [ 'javafx.controls', 'javafx.fxml' ]
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ events "passed", "skipped", "failed"
+
+ showExceptions true
+ exceptionFormat "full"
+ showCauses true
+ showStackTraces true
+ showStandardStreams = false
+ }
+}
+
+application {
+ mainClassName = "duke.Launcher"
+}
+
+shadowJar {
+ archiveBaseName = "duke"
+ archiveClassifier = null
+}
+
+checkstyle {
+ configFile = file('./config/checkstyle/checkstyle.xml')
+ toolVersion = '8.29'
+}
+
+run{
+ standardInput = System.in
+ enableAssertions = true
+}
\ No newline at end of file
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000000..e31724b2e4
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,398 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
index 8077118ebe..f146b049bb 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,29 +1,370 @@
-# User Guide
+#
-## Features
+# **User Guide** _for Jin Lin's Duke_
-### Feature-ABC
+#### _Duke manages your list of tasks for you. Be it to-dos, deadlines or events, Duke's got you._
-Description of the feature.
+#
-### Feature-XYZ
+## **Capabilities**
-Description of the feature.
+#
-## Usage
+### Track your list of tasks with their details!
-### `Keyword` - Describe action
+- Note down the tasks that you need to do and the relevant date and time applicable to them!
-Describe the action and its outcome.
+ The types of tasks supported are:
-Example of usage:
+ - To-dos _(with no date time details)_
+ - Deadlines _(with details on the date and time of the deadline)_
+ - Events (with details on the date and time of the event)
-`keyword (optional arguments)`
+- Manage the individual tasks in the list of tasks through the following actions:
+ - Marking the task as completed
+ - Updating the task description
+ - Updating the task date and line _(if applicable)_
+ - Deleting the task
+- Store the tasks in a local file to be loaded the next time the application is launched.
-Expected outcome:
+#
-Description of the outcome.
+## **Usage**
+
+#
+
+### **`list` - shows the list of tasks**
+
+_Prints out the list of tasks recorded. The message is limited to the first 4 tasks recorded._
+
+#### **Example of usage:**
+
+`list`
+
+#### **Example outcome:**
+
+A message listing the list of tasks.
+
+If there are no tasks, this will be expected:
+
+```
+There are currently no elements in the list!
+```
+
+otherwise, this will be expected:
+
+```
+These are the tasks currently stored in the list:
+1. Todo - Cut hair
+2. Deadline - CS2103/T iP (by Feb 18 2022 11.59pm)
+3. ...
+```
+
+#
+
+### **`todo (description)` - logs a new To-do**
+
+_Records a new To-do task to the list of tasks._
+
+#### **Example of usage:**
+
+`todo Cut hair`
+
+#### **Example outcome:**
+
+A new To-do task to be added to the list of tasks.
+
+This will be expected:
```
-expected output
+Got it. I've added this task:
+Todo - Cut hair
+Now you have 2 tasks in the list.
+
+> list
+
+These are the tasks currently stored in the list:
+1. ...
+2. Todo - Cut Hair
```
+
+#
+
+### **`deadline (description) /by (date) (time)` - logs a new Deadline**
+
+_Records a new Deadline task to the list of tasks._
+
+_Date input has to be in the format: yyyy-mm-dd e.g. 2022-04-18._
+
+_Time input has to be in the 24-hour format: hh:mm e.g. 19:33._
+
+#### **Example of usage:**
+
+`deadline Homework Submission /by 2022-01-13 23:59`
+
+#### **Example outcome:**
+
+A new Deadline task to be added to the list of tasks.
+
+This will be expected:
+
+```
+Got it. I've added this task:
+Deadline - Homework Submission (by Jan 13 2022 11.59pm)
+Now you have 1 tasks in the list.
+
+> list
+
+These are the tasks currently stored in the list:
+1. Deadline - Homework Submission (by Jan 13 2022 11.59pm)
+```
+
+#
+
+### **`event (description) /at (date) (time)` - logs a new Event**
+
+_Records a new Event task to the list of tasks._
+
+_Date input has to be in the format: yyyy-mm-dd e.g. 2022-04-18._
+
+_Time input has to be in the 24-hour format: hh:mm e.g. 19:33._
+
+#### **Example of usage:**
+
+`event Module Briefing /at 2022-02-21 09:00`
+
+#### **Example outcome:**
+
+A new Event task to be added to the list of tasks.
+
+This will be expected:
+
+```
+Got it. I've added this task:
+Event - Module Briefing (at Feb 21 2022 09.00am)
+Now you have 1 tasks in the list.
+
+> list
+
+These are the tasks currently stored in the list:
+1. Event - Module Briefing (at Feb 21 2022 09.00am)
+```
+
+#
+
+### **`delete (index)` - deletes the indexed task**
+
+_Deletes the task at the given index._
+
+#### **Example of usage:**
+
+`delete 1`
+
+#### **Example outcome:**
+
+The task at index 1 will be deleted.
+
+This will be expected:
+
+```
+Noted. I've removed this task:
+Event - Module Briefing (at Feb 21 2022 09.00am)
+Now you have 0 tasks in the list.
+
+> list
+
+There are currently no elements in the list!
+```
+
+#
+
+### **`mark (index)` - marks the indexed task as done**
+
+_Marks the task at the given index as Done._
+
+#### **Example of usage:**
+
+`mark 1`
+
+#### **Example outcome:**
+
+The task at index 1 will be marked as done.
+
+If the task has been previously marked as done, this will be expected:
+
+```
+The task is already marked.
+
+> list
+
+These are the tasks currently stored in the list:
+1. !!!DONE!!! Event - Module Briefing (at Feb 21 2022 09.00am)
+```
+
+else, this will be expected:
+
+```
+Nice! I've marked this task as done:
+!!!DONE!!! Event - Module Briefing (at Feb 21 2022 09.00am)
+
+> list
+
+These are the tasks currently stored in the list:
+1. !!!DONE!!! Event - Module Briefing (at Feb 21 2022 09.00am)
+```
+
+#
+
+### **`unmark (index)` - marks the indexed task as not yet done**
+
+_Marks the task at the given index as not yet done._
+
+#### **Example of usage:**
+
+`unmark 1`
+
+#### **Example outcome:**
+
+The task at index 1 will be marked as not yet done.
+
+If the task has been previously marked as not yet done, this will be expected:
+
+```
+The task is already unmarked.
+
+> list
+
+These are the tasks currently stored in the list:
+1. Event - Module Briefing (at Feb 21 2022 09.00am)
+```
+
+else, this will be expected:
+
+```
+Nice! I've marked this task as done:
+Event - Module Briefing (at Feb 21 2022 09.00am)
+
+> list
+
+These are the tasks currently stored in the list:
+1. Event - Module Briefing (at Feb 21 2022 09.00am)
+```
+
+#
+
+### **`clone (index)` - clones the indexed task**
+
+_Clones the task at the given index._
+
+#### **Example of usage:**
+
+`clone 1`
+
+#### **Example outcome:**
+
+The task at index 1 will be cloned.
+
+This will be expected:
+
+```
+I have cloned the task and added it to the end of the task list! This is the cloned task:
+Todo - Walk the dog
+
+> list
+
+These are the tasks currently stored in the list:
+1. Todo - Walk the dog
+2. Event - Module Briefing (at Feb 21 2022 09.00am)
+3. Todo - Walk the dog
+```
+
+#
+
+### **`update (what to update) (index) (new value)` - updates the indexed task**
+
+_Updates the specified detail of the task at the given index to the new value._
+
+_If date is to be updated, new value must follow the format: yyyy-mm-dd e.g. 2019-04-20._
+
+_If time is to be updated, new value must follow the format: hh:mm e.g. 23:59._
+
+#### **Example of usage:**
+
+`update time 1 23:00`
+
+#### **Example outcome:**
+
+The time of the task at index 1 will be updated.
+
+This will be expected:
+
+```
+I have updated the task as per your request! This is the updated task:
+Event - Module Briefing (at Feb 21 2022 11.00pm)
+
+> list
+
+These are the tasks currently stored in the list:
+1. Event - Module Briefing (at Feb 21 2022 11.00am)
+```
+
+#
+
+### **`find (keyword)` - searches for keyword**
+
+_Searches the list of tasks for tasks that contains the keyword._
+
+#### **Example of usage:**
+
+`find Bidding`
+
+#### **Example outcome:**
+
+The tasks that contains the keyword will be filtered out.
+
+If there are no tasks that contains the keyword, this will be expected:
+
+```
+There's nothing that contains the keyword!
+
+> list
+
+These are the tasks currently stored in the list:
+1. Event - Module Briefing (at Feb 21 2022 11.00am)
+2. Deadline - Module Bidding (by August 20 2022, 12:00pm)
+```
+
+else, this will be expected:
+
+```
+These are the matching tasks:
+1. Deadline - Module Bidding (by: Aug 20 2022 12:00pm)
+
+> list
+
+These are the tasks currently stored in the list:
+1. Event - Module Briefing (at Feb 21 2022 11.00am)
+2. Deadline - Module Bidding (by Aug 20 2022 12:00pm)
+```
+
+#
+
+### **`bye` - exits program**
+
+_Exits the program._
+
+#### **Example of usage:**
+
+`bye`
+
+#### **Example outcome:**
+
+A farewell message will be printed.
+
+This will be expected:
+
+```
+Sayonara!! Hope to see you again soon heh! :-)
+```
+
+#
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..7759176161
Binary files /dev/null and b/docs/Ui.png differ
diff --git a/docs/_config.yml b/docs/_config.yml
new file mode 100644
index 0000000000..259a24e4d2
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-tactile
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..7454180f2a
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..84d1f85fd6
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000000..1b6c787337
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000000..107acd32c4
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java
deleted file mode 100644
index 5d313334cc..0000000000
--- a/src/main/java/Duke.java
+++ /dev/null
@@ -1,10 +0,0 @@
-public class Duke {
- public static void main(String[] args) {
- String logo = " ____ _ \n"
- + "| _ \\ _ _| | _____ \n"
- + "| | | | | | | |/ / _ \\\n"
- + "| |_| | |_| | < __/\n"
- + "|____/ \\__,_|_|\\_\\___|\n";
- System.out.println("Hello from\n" + logo);
- }
-}
diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java
new file mode 100644
index 0000000000..d1abefb398
--- /dev/null
+++ b/src/main/java/duke/Duke.java
@@ -0,0 +1,56 @@
+package duke;
+
+import duke.admin.Parser;
+import duke.admin.Storage;
+import duke.admin.TaskList;
+import duke.admin.Ui;
+import duke.commands.Command;
+import duke.exceptions.DukeException;
+/**
+ * Duke class is the main class of the program.
+ */
+public class Duke {
+ private static final String DEFAULT_FILE_PATH = "./dukeSaveLog.txt";
+ private Storage storage;
+ private TaskList tasks;
+
+ /**
+ * Constructor of Duke that uses a default file path as the specified location of the storage file.
+ */
+ public Duke() {
+ storage = new Storage(DEFAULT_FILE_PATH);
+ try {
+ tasks = new TaskList(storage.load());
+ } catch (DukeException e) {
+ tasks = new TaskList();
+ }
+ }
+
+ /**
+ * Constructor of Duke that takes in a file path specifying the location of the
+ * storage file.
+ * @param filePath string specifying location of storage file
+ */
+ public Duke(String filePath) {
+ assert filePath != null;
+ storage = new Storage(filePath);
+ try {
+ tasks = new TaskList(storage.load());
+ } catch (DukeException e) {
+ tasks = new TaskList();
+ }
+ }
+
+ public String getResponse(String fullCommand) throws DukeException {
+ assert fullCommand != null;
+ try {
+ Command c = Parser.parse(fullCommand);
+ String response = c.execute(tasks, storage);
+
+ return response;
+ } catch (DukeException e) {
+ return Ui.showErrorMessage(e.getMessage());
+ }
+
+ }
+}
diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java
new file mode 100644
index 0000000000..e4ef6b4628
--- /dev/null
+++ b/src/main/java/duke/Launcher.java
@@ -0,0 +1,12 @@
+package duke;
+
+import javafx.application.Application;
+
+/**
+ * A launcher class to workaround classpath issues.
+ */
+public class Launcher {
+ public static void main(String[] args) {
+ Application.launch(Main.class, args);
+ }
+}
diff --git a/src/main/java/duke/Main.java b/src/main/java/duke/Main.java
new file mode 100644
index 0000000000..dddda9aa4d
--- /dev/null
+++ b/src/main/java/duke/Main.java
@@ -0,0 +1,33 @@
+package duke;
+
+import java.io.IOException;
+
+import duke.controllers.MainWindow;
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.layout.AnchorPane;
+import javafx.stage.Stage;
+
+/**
+ * A GUI for Duke using FXML.
+ */
+public class Main extends Application {
+
+ private Duke duke = new Duke();
+
+ @Override
+ public void start(Stage stage) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml"));
+ AnchorPane ap = fxmlLoader.load();
+ Scene scene = new Scene(ap);
+ stage.setScene(scene);
+ fxmlLoader.getController().setDuke(duke);
+ stage.setTitle("Duke");
+ stage.show();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/duke/admin/Parser.java b/src/main/java/duke/admin/Parser.java
new file mode 100644
index 0000000000..43e5a11cf1
--- /dev/null
+++ b/src/main/java/duke/admin/Parser.java
@@ -0,0 +1,66 @@
+package duke.admin;
+
+import duke.commands.AddCommand;
+import duke.commands.ChangeMarkCommand;
+import duke.commands.CloneCommand;
+import duke.commands.Command;
+import duke.commands.DeleteCommand;
+import duke.commands.ExitCommand;
+import duke.commands.FindCommand;
+import duke.commands.ListCommand;
+import duke.commands.UpdateCommand;
+import duke.exceptions.DukeException;
+
+/**
+ * Parser class parses the command passed in as a String and represents it as a
+ * Command that the program can manage.
+ */
+public class Parser {
+ private static String description;
+
+ /**
+ * Returns the relevent Command object to be executed from the string input.
+ * @param fullCommand the command as a String
+ * @return Command object that triggers an action from the program based on the
+ * command
+ * @throws DukeException exception thrown when command is invalid or improper
+ */
+ public static Command parse(String fullCommand) throws DukeException {
+ String action = fullCommand.split(" ", 2)[0];
+
+ if (fullCommand.split(" ", 2).length > 1) {
+ description = fullCommand.split(" ", 2)[1];
+ }
+
+ try {
+ switch (action) {
+ case "list": //List tasks function
+ return new ListCommand();
+ case "mark": //Mark tasks function
+ return new ChangeMarkCommand(description, true);
+ case "unmark": //Unmark tasks function
+ return new ChangeMarkCommand(description, false);
+ case "todo": //Add To Do task function
+ return new AddCommand("T", description);
+ case "deadline": //Add Deadline task function
+ return new AddCommand("D", description);
+ case "event": //Add Event task function
+ return new AddCommand("E", description);
+ case "delete": //Deletes task function
+ return new DeleteCommand(description);
+ case "bye": //Exit function
+ return new ExitCommand();
+ case "find": //Find task function
+ return new FindCommand(description);
+ case "update": //Update task function
+ return new UpdateCommand(description);
+ case "clone": //Clone task function
+ return new CloneCommand(description);
+ default:
+ throw new DukeException(DukeException.INVALID_COMMAND);
+ }
+ } catch (NullPointerException ne) {
+ throw new DukeException(DukeException.INVALID_FORMAT);
+ }
+ }
+}
diff --git a/src/main/java/duke/admin/Storage.java b/src/main/java/duke/admin/Storage.java
new file mode 100644
index 0000000000..875d4df208
--- /dev/null
+++ b/src/main/java/duke/admin/Storage.java
@@ -0,0 +1,357 @@
+package duke.admin;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import duke.exceptions.DukeException;
+import duke.tasks.Task;
+
+/**
+ * Storage class manages the storage file and the actions on the storage file.
+ */
+public class Storage {
+ private static String buffer = " xxx ";
+ private File storageFile;
+
+ /**
+ * Constructor for Storage that takes in a file path that points to the storage
+ * file
+ * @param filePath
+ */
+ public Storage(String filePath) {
+ this.storageFile = new File(filePath);
+ }
+
+ /**
+ * Reads the content of the storage file and passes the data to be processed by
+ * the program.
+ * @return an array of Strings each representing the tasks stored in the storage
+ * file
+ * @throws DukeException exception thrown when file cannot be found or is
+ * corrupted
+ */
+ public String[] load() throws DukeException {
+ try {
+ BufferedReader storageFileReader = new BufferedReader(new FileReader(this.storageFile));
+ String fileContent = "";
+ String dataLine = storageFileReader.readLine();
+
+ while (dataLine != null) {
+ fileContent = fileContent + dataLine + System.lineSeparator();
+
+ dataLine = storageFileReader.readLine();
+ }
+ String[] tasksArr = fileContent.split(System.lineSeparator());
+ storageFileReader.close();
+
+ return tasksArr;
+ } catch (FileNotFoundException e) {
+ throw new DukeException("File not found!! :-(");
+ } catch (IOException e) {
+ throw new DukeException("Issue reading file!! D:");
+ }
+ }
+
+ /**
+ * Reflects the addition of the task in the storage list.
+ * @param task the task that has just been added to the task list
+ * @throws DukeException exception thrown when there is an error accessing or
+ * writing to the storage file
+ */
+ public void updateAfterAdd(Task task, String description) throws DukeException {
+ try {
+ int isMarked = task.isDone() ? 1 : 0; // isMarked is the integer representation of task.isDone();
+
+ FileWriter storageFileWriter = new FileWriter(storageFile, true);
+ storageFileWriter.write(task.getType() + buffer + isMarked + buffer + description + "\n");
+ storageFileWriter.close();
+ } catch (IOException e) {
+ throw new DukeException("Cannot update addition in save file!! :-(");
+ }
+ }
+
+ /**
+ * Updates the storage file after details of tasks have been updated.
+ * @param typeOfTask type of task that has been updated
+ * @param typeOfUpdate what details has been updated: description, date or time
+ * @param index index of task that has been updated
+ * @param updateValue value to be updated to
+ * @throws DukeException exception thrown when there is an error accessing or writing to the storage file
+ */
+ public void updateAfterEdits(String typeOfTask, String typeOfUpdate, int index, String updateValue)
+ throws DukeException {
+ try {
+ int dataLineCounter = 0; //initializing the counter
+ BufferedReader storageFileReader = new BufferedReader(new FileReader(storageFile));
+ String contentToBeWritten = "";
+ String dataLine = storageFileReader.readLine();
+
+ while (dataLine != null) {
+ if (dataLineCounter == index) {
+ if (typeOfTask.equals("D") || typeOfTask.equals("E")) {
+ dataLine = updateNonToDoTask(typeOfUpdate, dataLine, updateValue);
+ } else if (typeOfTask.equals("T")) {
+ dataLine = updateToDoTask(typeOfUpdate, dataLine, updateValue);
+ }
+ }
+
+ contentToBeWritten = contentToBeWritten + dataLine + System.lineSeparator();
+ dataLine = storageFileReader.readLine();
+ dataLineCounter++;
+ }
+
+ FileWriter storageFileWriter = new FileWriter(storageFile);
+ storageFileWriter.write(contentToBeWritten);
+
+ storageFileReader.close();
+ storageFileWriter.close();
+ } catch (FileNotFoundException e) {
+ throw new DukeException("Save file not found!! :-(");
+ } catch (IOException e) {
+ throw new DukeException("Cannot update edits in save file!! D:");
+ }
+ }
+
+ /**
+ * Updates the non-"To Do" tasks stored in the storage file
+ * @param typeOfUpdate whether description, date or time should be updated
+ * @param original original To Do task to be updated
+ * @param updateValue value to be updated to
+ * @return updated non-"To Do" task to be stored formatted as a String
+ */
+ private String updateNonToDoTask(String typeOfUpdate, String original, String updateValue) {
+ String updatedString = "";
+
+ switch (typeOfUpdate) {
+ case "description":
+ updatedString = updateDescription(original, updateValue);
+ break;
+ case "date":
+ updatedString = updateDate(original, updateValue);
+ break;
+ case "time":
+ updatedString = updateTime(original, updateValue);
+ break;
+ default:
+ break;
+ }
+
+ return updatedString;
+ }
+
+ /**
+ * Updates the To Do tasks stored in the storage file
+ * @param typeOfUpdate should be description only
+ * @param original original To Do task to be updated
+ * @param updateValue value to be updated to
+ * @return updated To Do task to be stored formatted as a String
+ */
+ private String updateToDoTask(String typeOfUpdate, String original, String updateValue) {
+ String updatedString = "";
+
+ switch (typeOfUpdate) {
+ case "description":
+ updatedString = updateToDoDescription(original, updateValue);
+ break;
+ default:
+ break;
+ }
+
+ return updatedString;
+ }
+
+ /**
+ * Replaces the description in the original task stored in the storage file
+ * @param original original task stored in the storage file
+ * @param updateValue description to be updated to
+ * @return updated task to be stored formatted as a String
+ */
+ private String updateDescription(String original, String updateValue) {
+ int numOfCharsBeforeDescription = 12;
+ int numOfCharsAfterDescription = 21;
+
+ String prefix = original.substring(0, numOfCharsBeforeDescription);
+ String suffix = original.substring(original.length() - numOfCharsAfterDescription);
+
+ return prefix + updateValue + suffix;
+ }
+
+ /**
+ * Replaces the description of the to do task stored within the storage file
+ * @param original original task stored in the storage file
+ * @param updateValue description to be updated to
+ * @return updated task to be stored formatted as a String
+ */
+ private String updateToDoDescription(String original, String updateValue) {
+ int numOfCharsBeforeDescription = 12;
+
+ String prefix = original.substring(0, numOfCharsBeforeDescription);
+
+ return prefix + updateValue;
+ }
+
+ /**
+ * Replaces the date of the tasks stored within the storage file
+ * @param original original task stored in the storage file
+ * @param updateValue date to be updated to
+ * @return updated task to be stored formatted as a String
+ */
+ private String updateDate(String original, String updateValue) {
+ int numOfCharsFromDate = 16;
+ int numOfCharsAfterDate = 6;
+ int indexOfDate = original.length() - numOfCharsFromDate;
+
+ String dateToBeUpdated = original.substring(indexOfDate, original.length() - numOfCharsAfterDate);
+
+ return original.replaceFirst(dateToBeUpdated, updateValue);
+ }
+
+ /**
+ * Replaces the time of the tasks stored within the storage file
+ * @param original original task stored in the storage file
+ * @param updateValue time to be updated to
+ * @return updated task to be stored formatted as a String
+ */
+ private String updateTime(String original, String updateValue) {
+ int numOfCharsFromTime = 5;
+
+ String timeToBeUpdated = original.substring(original.length() - numOfCharsFromTime);
+
+ return original.replaceFirst(timeToBeUpdated, updateValue);
+ }
+
+ /**
+ * Updates the storage list after a clone action has been performed.
+ * @param index index of task to be cloned
+ * @throws DukeException exception thrown when there is an error accessing or writing to the storage file
+ */
+ public void updateAfterClone(int index) throws DukeException {
+ try {
+ int dataLineCounter = 0;
+ BufferedReader storageFileReader = new BufferedReader(new FileReader(storageFile));
+ String contentToBeWritten = "";
+ String dataLine = storageFileReader.readLine();
+ String clone = "";
+
+ while (dataLine != null) {
+ if (dataLineCounter == index) {
+ clone = clone + dataLine;
+ }
+
+ contentToBeWritten = contentToBeWritten + dataLine + System.lineSeparator();
+ dataLine = storageFileReader.readLine();
+ dataLineCounter++;
+ }
+
+ contentToBeWritten = contentToBeWritten + clone + System.lineSeparator();
+ FileWriter storageFileWriter = new FileWriter(storageFile);
+ storageFileWriter.write(contentToBeWritten);
+
+ storageFileReader.close();
+ storageFileWriter.close();
+ } catch (FileNotFoundException e) {
+ throw new DukeException("Save file not found!! :-(");
+ } catch (IOException e) {
+ throw new DukeException("Cannot update clone action in save file!! D:");
+ }
+ }
+
+ /**
+ * Reflects the deleted task in the storage list.
+ * @param index the index of the task that has just been deleted
+ * @throws DukeException exception thrown when there is an error accessing or
+ * writing to the storage file
+ */
+ public void updateAfterDelete(int index) throws DukeException {
+ try {
+ int dataLineCounter = 0; //initializing the counter
+ BufferedReader storageFileReader = new BufferedReader(new FileReader(storageFile));
+ String contentToBeWritten = "";
+ String dataLine = storageFileReader.readLine();
+
+ while (dataLine != null) {
+ if (dataLineCounter != index) {
+ contentToBeWritten = contentToBeWritten + dataLine + System.lineSeparator();
+ }
+
+ dataLine = storageFileReader.readLine();
+ dataLineCounter++;
+ }
+
+ FileWriter storageFileWriter = new FileWriter(storageFile);
+ storageFileWriter.write(contentToBeWritten);
+
+ storageFileReader.close();
+ storageFileWriter.close();
+ } catch (FileNotFoundException e) {
+ throw new DukeException("Save file not found!! :-(");
+ } catch (IOException e) {
+ throw new DukeException("Cannot update deletion in save file!! D:");
+ }
+ }
+
+ /**
+ * Updates the storage file when there has just been a change to whether a task
+ * has been marked as done or not yet done.
+ * @param index index of the task which the action is performed on
+ * @param toMark true if the action marked the task as done, otherwise
+ * false
+ * @throws DukeException exception thrown when there is an error accessing or
+ * writing to the storage file
+ */
+ public void updateAfterChangeMark(int index, boolean toMark) throws DukeException {
+ try {
+ int dataLineCounter = 0; //initializing the counter
+ BufferedReader storageFileReader = new BufferedReader(new FileReader(storageFile));
+ String contentToBeWritten = "";
+ String dataLine = storageFileReader.readLine();
+
+ while (dataLine != null) {
+ if (dataLineCounter == index) {
+ if (dataLine.charAt(6) == '1') {
+ if (!(toMark)) {
+ dataLine = dataLine.replaceFirst("xxx 1 xxx", "xxx 0 xxx");
+ }
+ } else {
+ if (toMark) {
+ dataLine = dataLine.replaceFirst("xxx 0 xxx", "xxx 1 xxx");
+ }
+ }
+ }
+
+ contentToBeWritten = contentToBeWritten + dataLine + System.lineSeparator();
+ dataLine = storageFileReader.readLine();
+ dataLineCounter++;
+ }
+
+ FileWriter storageFileWriter = new FileWriter(storageFile);
+ storageFileWriter.write(contentToBeWritten);
+
+ storageFileReader.close();
+ storageFileWriter.close();
+ } catch (FileNotFoundException e) {
+ throw new DukeException("Save file not found!! :-(");
+ } catch (IOException e) {
+ throw new DukeException("Cannot update edits in save file!! D:");
+ }
+ }
+
+ /**
+ * Wipes the storage file of all its data.
+ * @throws DukeException exception thrown when there is an error accessing or
+ * writing to the storage file
+ */
+ public void resetFile() throws DukeException {
+ try {
+ FileWriter storageFileWriter = new FileWriter(storageFile, false);
+ storageFileWriter.write("");
+ storageFileWriter.close();
+ } catch (IOException e) {
+ throw new DukeException("Cannot reset the save file!! D:");
+ }
+ }
+}
diff --git a/src/main/java/duke/admin/TaskList.java b/src/main/java/duke/admin/TaskList.java
new file mode 100644
index 0000000000..ec33f7fa7c
--- /dev/null
+++ b/src/main/java/duke/admin/TaskList.java
@@ -0,0 +1,205 @@
+package duke.admin;
+
+import java.util.ArrayList;
+
+import duke.exceptions.DukeException;
+import duke.tasks.Deadline;
+import duke.tasks.Event;
+import duke.tasks.Task;
+import duke.tasks.ToDo;
+import duke.tasks.Trigger;
+
+/**
+ * TaskList class manages the task list and the actions that can be performed on
+ * the task list.
+ */
+public class TaskList {
+ private static final String BUFFER = " xxx ";
+ private static final Trigger noChangeTrigger = new Trigger("n0 cH4Ng3$ d#t3CtEd");
+ private ArrayList tasks;
+
+ /**
+ * Constructor for TaskList that takes in data from the storage file and
+ * constructs a task list from there.
+ * @param tasksArr data read from the storage file
+ * @throws DukeException exception when data is corrupted or task list cannot be
+ * created as intended
+ */
+ public TaskList(String[] tasksArr) throws DukeException {
+ try {
+ tasks = new ArrayList<>();
+
+ for (String task : tasksArr) {
+ String[] taskDetails = task.split(BUFFER);
+
+ String type = taskDetails[0];
+ Boolean isMarked = (Integer.parseInt(taskDetails[1]) > 0);
+ String description = taskDetails[2];
+
+ switch (type) {
+ case "T":
+ tasks.add(new ToDo(description, isMarked));
+ break;
+ case "D":
+ tasks.add(new Deadline(description, isMarked));
+ break;
+ case "E":
+ tasks.add(new Event(description, isMarked));
+ break;
+ default:
+ break;
+ }
+ }
+ } catch (Exception e) {
+ throw new DukeException("");
+ }
+ }
+
+ /**
+ * Constructor for TaskList when storage file data is unavailable.
+ */
+ public TaskList() {
+ tasks = new ArrayList<>();
+ }
+
+ /**
+ * Adds the task specified to the task list.
+ * @param task the task to be added
+ */
+ public void add(Task task) {
+ tasks.add(task);
+ }
+
+ /**
+ * Deletes the task indexed by the index specified.
+ * @param index the index of the task that is to be deleted
+ * @return the task after it is deleted from the task list
+ */
+ public Task delete(int index) {
+ assert index <= tasks.size();
+ Task deletedTask = tasks.get(index);
+ tasks.remove(index);
+
+ return deletedTask;
+ }
+
+ /**
+ * Prints out all the tasks in the task list and their index.
+ * @return task list as a string
+ */
+ public String list() {
+ String listString = "";
+
+ for (int i = 1; i <= tasks.size(); i++) {
+ Task task = tasks.get(i - 1);
+ String listElement = i + ". " + task.toString();
+
+ listString = listString + listElement + System.lineSeparator();
+ }
+
+ return listString;
+ }
+
+ /**
+ * Prints out all the tasks in the task list that contains the keyword.
+ * @param keyword keyword to be contained by the tasks
+ * @return result as a string
+ */
+ public String find(String keyword) {
+ ArrayList searchResults = new ArrayList<>();
+
+ for (int i = 0; i < tasks.size(); i++) {
+ Task task = tasks.get(i);
+ String taskAsString = task.toString().toLowerCase();
+ if (taskAsString.contains(keyword.toLowerCase())) {
+ searchResults.add(task);
+ }
+ }
+
+ String searchResultString = "";
+
+ for (int j = 1; j <= searchResults.size(); j++) {
+ Task matchedTask = searchResults.get(j - 1);
+ String result = j + ". " + matchedTask.toString();
+ searchResultString = searchResultString + result + System.lineSeparator();
+ }
+
+ if (searchResultString.equals("")) {
+ return "There's nothing that contains the keyword!";
+ } else {
+ return "These are the matching tasks:\n" + searchResultString;
+ }
+ }
+
+ /**
+ * Changes the mark of the task if the command requests for a change in mark and returns the task that has been
+ * changed. If not, return a trigger task to trigger the system to inform the user that the command does not
+ * change the task.
+ * @param index index of task to be marked
+ * @param toMark if the command wishes the indexed task to be marked or not
+ * @return the changed task or a trigger task
+ */
+ public Task changeMark(int index, boolean toMark) {
+ assert index <= tasks.size();
+
+ Task taskToChange = tasks.get(index);
+ boolean isMarked = taskToChange.isDone();
+ boolean isChangingMark = isMarked ^ toMark;
+
+ if (isChangingMark) {
+ taskToChange.toggleMark();
+ return taskToChange;
+ } else {
+ return noChangeTrigger;
+ }
+
+ }
+
+ /**
+ * Returns the number of tasks in the task list.
+ * @return number of tasks in the task list
+ */
+ public int getNumberOfTasks() {
+ return tasks.size();
+ }
+
+ /**
+ * Updates the details of the indexed task and returns the resulting task.
+ * @param type details to be updated: description, date or time
+ * @param index index of task to be updated
+ * @param updateValue value to be updated to
+ * @return task that has been updated
+ * @throws DukeException when there is an error trying to update the date or time value
+ */
+ public Task update(String type, int index, String updateValue) throws DukeException {
+ Task task = tasks.get(index);
+
+ switch (type) {
+ case "description":
+ task.updateDescription(updateValue);
+ break;
+ case "date":
+ task.updateDate(updateValue);
+ break;
+ case "time":
+ task.updateTime(updateValue);
+ break;
+ default:
+ throw new DukeException(DukeException.INVALID_FORMAT);
+ }
+
+ return task;
+ }
+
+ /**
+ * Clones the indexed task and adds to the the end of the task list. Returns the cloned task at the end.
+ * @param index index of the task to be cloned
+ * @return the task that is cloned
+ */
+ public Task clone(int index) {
+ Task taskToBeCloned = tasks.get(index);
+ tasks.add(taskToBeCloned);
+
+ return taskToBeCloned;
+ }
+}
diff --git a/src/main/java/duke/admin/Ui.java b/src/main/java/duke/admin/Ui.java
new file mode 100644
index 0000000000..81e3515502
--- /dev/null
+++ b/src/main/java/duke/admin/Ui.java
@@ -0,0 +1,133 @@
+package duke.admin;
+
+import duke.tasks.Task;
+import duke.tasks.Trigger;
+
+/**
+ * Ui is a class that manages the bulk of the user interaction required by the
+ * program.
+ */
+public class Ui {
+ private static final Trigger noChangeTrigger = new Trigger("n0 cH4Ng3$ d#t3CtEd");
+
+ /**
+ * Prints out a welcome message when the user boots the program.
+ */
+ public static String showWelcomeMessage() {
+ return "Welcome to Duke, your friendly task manager!\n What do you want to do today?";
+ }
+
+ /**
+ * Prints out a farewell message when the user leaves the
+ * program.
+ */
+ public static String showGoodByeMessage() {
+ return "Sayonara!! Hope to see you again soon hehe! :-)";
+ }
+
+ /**
+ * Prints out a message to let the user know what task has
+ * been added and how many tasks there are currently in the task list.
+ * @param task the task that has just been added into the task list
+ * @param tasks the list of task being managed by Duke
+ */
+ public static String showAddedMessage(Task task, TaskList tasks) {
+ return "Got it. I've added this task:\n" + task.toString()
+ + "\nNow you have " + tasks.getNumberOfTasks() + " tasks in the list.";
+ }
+
+ /**
+ * Prints out a message to let the user know what task has
+ * been deleted and how many tasks there are remaining in the task list.
+ * @param task the task that has just been deleted into the task list
+ * @param tasks the list of task being managed by Duke
+ */
+ public static String showDeletedMessage(Task task, TaskList tasks) {
+ return "Noted. I've removed this task:\n" + task.toString()
+ + "\nNow you have " + tasks.getNumberOfTasks() + " tasks in the list.";
+ }
+
+ /**
+ * Returns a string to inform the user that the mark is changed.
+ * @param task task to be changed
+ * @param toMark if the command wishes for the task to be marked or not.
+ * @return a string to inform the user that the mark is changed.
+ */
+ public static String showChangeMarkMessage(Task task, boolean toMark) {
+ if (task.toString().equals(noChangeTrigger.toString())) {
+ String message = "The task is already ";
+ String messageSuffix = toMark ? "marked." : "unmarked.";
+
+ return message + messageSuffix;
+ } else {
+ String taskDescription = task.toString();
+ String messagePrefix = toMark ? "Nice! I've marked this task as done:\n"
+ : "OK, I've marked this task as not done yet:\n";
+
+ return messagePrefix + taskDescription;
+ }
+ }
+
+ /**
+ * Prints out the error message to the user to let the user
+ * know why the program cannot run as intended
+ * @param errorMessage a message describing the fault
+ */
+ public static String showErrorMessage(String errorMessage) {
+ return "Uh oh... We ran into an error: " + errorMessage;
+ }
+
+ /**
+ * Returns the list of tasks as a String to be printed out in response to user's request to print the task list.
+ * @param tasks list of tasks to be printed
+ * @return the list of tasks as a String with Ui features included
+ */
+ public static String listTasks(TaskList tasks) {
+ String listResult = tasks.list();
+
+ if (listResult.equals("")) {
+ return "There are currently no elements in the list!";
+ } else {
+ String listResultPrefix = "These are the tasks currently stored in the list:\n";
+
+ return listResultPrefix + listResult;
+ }
+ }
+
+ /**
+ * Returns a string of tasks containing the keyword.
+ * @param tasks list of tasks to be searched
+ * @param keyword the keyword that the results have to contain
+ * @return the list of matching tasks as a String to be printed
+ */
+ public static String findTasks(TaskList tasks, String keyword) {
+ String findResult = tasks.find(keyword);
+
+ if (findResult.equals("")) {
+ return "No results containing the keyword found!";
+ } else {
+ String findResultPrefix = "Here are the matching tasks in your list:" + System.lineSeparator();
+
+ return findResultPrefix + findResult;
+ }
+ }
+
+ /**
+ * Returns a string informing user that update has been completed.
+ * @param task task to be updated
+ * @return a string informing user that the task is updated and shows the user the updated task
+ */
+ public static String showUpdatedMessage(Task task) {
+ return "I have updated the task as per your request! This is the updated task:\n" + task.toString();
+ }
+
+ /**
+ * Returns a string informing user that the cloning action is completed.
+ * @param task the cloned task
+ * @return a string detailing the cloned task and that cloning is complete.
+ */
+ public static String showClonedMessage(Task task) {
+ return "I have cloned the task and added it to the end of the task list! This is the cloned task:\n"
+ + task.toString();
+ }
+}
diff --git a/src/main/java/duke/commands/AddCommand.java b/src/main/java/duke/commands/AddCommand.java
new file mode 100644
index 0000000000..3fa0dc6b6f
--- /dev/null
+++ b/src/main/java/duke/commands/AddCommand.java
@@ -0,0 +1,70 @@
+package duke.commands;
+
+import duke.admin.Storage;
+import duke.admin.TaskList;
+import duke.admin.Ui;
+import duke.exceptions.DukeException;
+import duke.tasks.Deadline;
+import duke.tasks.Event;
+import duke.tasks.Task;
+import duke.tasks.ToDo;
+
+/**
+ * AddCommand is a Command that adds a task that is either a ToDo task, Deadline
+ * task or Event task to the program.
+ */
+
+public class AddCommand extends Command {
+ private String description;
+ private String type;
+ private Task taskToBeAdded;
+
+ /**
+ * Constructor for AddCommand which takes in the type of task and description of
+ * task that is to be added.
+ * @param type type of task to be added
+ * @param description description of task to be added
+ */
+ public AddCommand(String type, String description) {
+ assert type != null;
+ assert description != null;
+ this.type = type;
+ this.description = description;
+ }
+
+ /**
+ * Adds the task to TaskList, updates the storage file and notifies the user
+ * when it's done
+ * @param tasks task list local to user
+ * @param storage storage instance local to user
+ */
+ @Override
+ public String execute(TaskList tasks, Storage storage) throws DukeException {
+ switch (type) {
+ case "T":
+ taskToBeAdded = new ToDo(description);
+ break;
+ case "D":
+ taskToBeAdded = new Deadline(description);
+ break;
+ case "E":
+ taskToBeAdded = new Event(description);
+ break;
+ default:
+ break;
+ }
+
+ tasks.add(taskToBeAdded);
+ storage.updateAfterAdd(taskToBeAdded, description);
+
+ return Ui.showAddedMessage(taskToBeAdded, tasks);
+ }
+
+ /**
+ * Checks if this is an exit command, and only returns true for an exit command.
+ */
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/duke/commands/ChangeMarkCommand.java b/src/main/java/duke/commands/ChangeMarkCommand.java
new file mode 100644
index 0000000000..51a6091e4a
--- /dev/null
+++ b/src/main/java/duke/commands/ChangeMarkCommand.java
@@ -0,0 +1,44 @@
+package duke.commands;
+
+import duke.admin.Storage;
+import duke.admin.TaskList;
+import duke.admin.Ui;
+import duke.exceptions.DukeException;
+import duke.tasks.Task;
+
+/**
+ * ChangeMarkCommand changes the marked status of a task, either by marking it or unmarking it.
+ */
+public class ChangeMarkCommand extends Command {
+ private String description;
+ private boolean isMarkRequest;
+
+ /**
+ * Constructor for ChangeMarkCommand
+ * @param index index of task to be changed
+ * @param isMarkRequest whether the command wants to mark the task or unmark the task
+ */
+ public ChangeMarkCommand(String description, boolean isMarkRequest) {
+ this.description = description;
+ this.isMarkRequest = isMarkRequest;
+ }
+
+ @Override
+ public String execute(TaskList tasks, Storage storage) throws DukeException {
+ int index = Integer.parseInt(description) - 1;
+
+ if (index >= tasks.getNumberOfTasks() || index < 0) {
+ throw new DukeException(DukeException.INVALID_FORMAT);
+ }
+
+ Task task = tasks.changeMark(index, isMarkRequest);
+ storage.updateAfterChangeMark(index, isMarkRequest);
+
+ return Ui.showChangeMarkMessage(task, isMarkRequest);
+ }
+
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/duke/commands/CloneCommand.java b/src/main/java/duke/commands/CloneCommand.java
new file mode 100644
index 0000000000..16d3abd8ef
--- /dev/null
+++ b/src/main/java/duke/commands/CloneCommand.java
@@ -0,0 +1,41 @@
+package duke.commands;
+
+import duke.admin.Storage;
+import duke.admin.TaskList;
+import duke.admin.Ui;
+import duke.exceptions.DukeException;
+import duke.tasks.Task;
+
+/**
+ * CloneCommand clones the indexed task specified in the command.
+ */
+public class CloneCommand extends Command {
+ private String description;
+
+ /**
+ * Constructor for CloneCommand that takes in a description.
+ * @param description of clone command.
+ */
+ public CloneCommand(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public String execute(TaskList tasks, Storage storage) throws DukeException {
+ int index = Integer.parseInt(description) - 1;
+
+ if (index >= tasks.getNumberOfTasks() || index < 0) {
+ throw new DukeException(DukeException.INVALID_FORMAT);
+ }
+
+ Task task = tasks.clone(index);
+ storage.updateAfterClone(index);
+
+ return Ui.showClonedMessage(task);
+ }
+
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/duke/commands/Command.java b/src/main/java/duke/commands/Command.java
new file mode 100644
index 0000000000..0002116483
--- /dev/null
+++ b/src/main/java/duke/commands/Command.java
@@ -0,0 +1,16 @@
+package duke.commands;
+
+import duke.admin.Storage;
+import duke.admin.TaskList;
+import duke.exceptions.DukeException;
+
+/**
+ * Command is an abstract class that specifies 2 methods that has to be
+ * implemented by all the different commands, namely isExit and execute.
+ */
+public abstract class Command {
+
+ public abstract boolean isExit();
+
+ public abstract String execute(TaskList tasks, Storage storage) throws DukeException;
+}
diff --git a/src/main/java/duke/commands/DeleteCommand.java b/src/main/java/duke/commands/DeleteCommand.java
new file mode 100644
index 0000000000..3fd66cbfcb
--- /dev/null
+++ b/src/main/java/duke/commands/DeleteCommand.java
@@ -0,0 +1,52 @@
+package duke.commands;
+
+import duke.admin.Storage;
+import duke.admin.TaskList;
+import duke.admin.Ui;
+import duke.exceptions.DukeException;
+import duke.tasks.Task;
+
+/**
+ * DeleteCommand is a Command that deletes the task at the index specified from
+ * the program.
+ */
+public class DeleteCommand extends Command {
+ private String description;
+
+ /**
+ * Constructor for DeleteCommand that takes in the description containing index
+ * of the task to be deleted from the program.
+ * @param description description of delete command
+ */
+ public DeleteCommand(String description) {
+ this.description = description;
+ }
+
+ /**
+ * Deletes the indexed task from task list and storage file and updates the user
+ * when the task is deleted.
+ * @param tasks task list local to user
+ * @param storage storage instance local to user
+ */
+ @Override
+ public String execute(TaskList tasks, Storage storage) throws DukeException {
+ int index = Integer.parseInt(description) - 1;
+
+ if (index >= tasks.getNumberOfTasks() || index < 0) {
+ throw new DukeException(DukeException.INVALID_FORMAT);
+ }
+
+ Task deletedTask = tasks.delete(index);
+ storage.updateAfterDelete(index);
+
+ return Ui.showDeletedMessage(deletedTask, tasks);
+ }
+
+ /**
+ * Checks if this is an exit command, and only returns true for an exit command.
+ */
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/duke/commands/ExitCommand.java b/src/main/java/duke/commands/ExitCommand.java
new file mode 100644
index 0000000000..cfae7a4e79
--- /dev/null
+++ b/src/main/java/duke/commands/ExitCommand.java
@@ -0,0 +1,30 @@
+package duke.commands;
+
+import duke.admin.Storage;
+import duke.admin.TaskList;
+import duke.admin.Ui;
+
+/**
+ * ExitCommand is a Command that terminates the program.
+ */
+public class ExitCommand extends Command {
+
+ /**
+ * Checks if this is an exit command, and only returns yes for an
+ * exit command.
+ */
+ @Override
+ public boolean isExit() {
+ return true;
+ }
+
+ /**
+ * Prints a farewell message to the user and exits the program.
+ * @param tasks task list local to user
+ * @param storage storage instance local to user
+ */
+ @Override
+ public String execute(TaskList tasks, Storage storage) {
+ return Ui.showGoodByeMessage();
+ }
+}
diff --git a/src/main/java/duke/commands/FindCommand.java b/src/main/java/duke/commands/FindCommand.java
new file mode 100644
index 0000000000..2b1bda7bb5
--- /dev/null
+++ b/src/main/java/duke/commands/FindCommand.java
@@ -0,0 +1,31 @@
+package duke.commands;
+
+import duke.admin.Storage;
+import duke.admin.TaskList;
+import duke.exceptions.DukeException;
+
+public class FindCommand extends Command {
+ private String keyword;
+
+ /**
+ * Constructor for FindCommand that takes in a keyword to be used to filter the matching results.
+ * @param keyword
+ */
+ public FindCommand(String keyword) {
+ this.keyword = keyword;
+ }
+
+ @Override
+ public String execute(TaskList tasks, Storage storage) throws DukeException {
+ if (keyword == null) {
+ throw new DukeException(DukeException.INVALID_FORMAT);
+ }
+
+ return tasks.find(keyword);
+ }
+
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/duke/commands/ListCommand.java b/src/main/java/duke/commands/ListCommand.java
new file mode 100644
index 0000000000..5d88810acb
--- /dev/null
+++ b/src/main/java/duke/commands/ListCommand.java
@@ -0,0 +1,31 @@
+package duke.commands;
+
+import duke.admin.Storage;
+import duke.admin.TaskList;
+import duke.admin.Ui;
+
+/**
+ * ListCommand is a Command that triggers the program to print out the task list
+ * with proper indexing according to chronological order of when the task was
+ * added into the task list.
+ */
+public class ListCommand extends Command {
+
+ /**
+ * Lists out the tasks stored in the tasks list.
+ * @param tasks task list local to user
+ * @param storage storage instance local to user
+ */
+ @Override
+ public String execute(TaskList tasks, Storage storage) {
+ return Ui.listTasks(tasks);
+ }
+
+ /**
+ * Checks if this is an exit command, and only returns true for an exit command.
+ */
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/duke/commands/UpdateCommand.java b/src/main/java/duke/commands/UpdateCommand.java
new file mode 100644
index 0000000000..9a93b0d82d
--- /dev/null
+++ b/src/main/java/duke/commands/UpdateCommand.java
@@ -0,0 +1,41 @@
+package duke.commands;
+
+import duke.admin.Storage;
+import duke.admin.TaskList;
+import duke.admin.Ui;
+import duke.exceptions.DukeException;
+import duke.tasks.Task;
+
+public class UpdateCommand extends Command {
+ private int index;
+ private String typeOfUpdate;
+ private String updateValue;
+
+ /**
+ * Constructor for UpdateCommand
+ * @param description description of the command that contains the type of update, index of task to be updated,
+ * and value to be updated to
+ */
+ public UpdateCommand(String description) {
+ String[] splitDescription = description.split(" ");
+ assert splitDescription.length >= 3;
+
+ this.typeOfUpdate = splitDescription[0];
+ this.index = Integer.parseInt(splitDescription[1]) - 1;
+ this.updateValue = splitDescription[2];
+ }
+
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+
+ @Override
+ public String execute(TaskList tasks, Storage storage) throws DukeException {
+ Task taskToBeUpdated = tasks.update(typeOfUpdate, index, updateValue);
+ String typeOfTask = taskToBeUpdated.getType();
+ storage.updateAfterEdits(typeOfTask, typeOfUpdate, index, updateValue);
+
+ return Ui.showUpdatedMessage(taskToBeUpdated);
+ }
+}
diff --git a/src/main/java/duke/controllers/DialogBox.java b/src/main/java/duke/controllers/DialogBox.java
new file mode 100644
index 0000000000..cb2ea03e55
--- /dev/null
+++ b/src/main/java/duke/controllers/DialogBox.java
@@ -0,0 +1,61 @@
+package duke.controllers;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.HBox;
+
+/**
+ * An example of a custom control using FXML.
+ * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label
+ * containing text from the speaker.
+ */
+public class DialogBox extends HBox {
+ @FXML
+ private Label dialog;
+ @FXML
+ private ImageView displayPicture;
+
+ private DialogBox(String text, Image img) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml"));
+ fxmlLoader.setController(this);
+ fxmlLoader.setRoot(this);
+ fxmlLoader.load();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ dialog.setText(text);
+ displayPicture.setImage(img);
+ }
+
+ /**
+ * Flips the dialog box such that the ImageView is on the left and text on the right.
+ */
+ private void flip() {
+ ObservableList tmp = FXCollections.observableArrayList(this.getChildren());
+ Collections.reverse(tmp);
+ getChildren().setAll(tmp);
+ setAlignment(Pos.TOP_LEFT);
+ }
+
+ public static DialogBox getUserDialog(String text, Image img) {
+ return new DialogBox(text, img);
+ }
+
+ public static DialogBox getDukeDialog(String text, Image img) {
+ var db = new DialogBox(text, img);
+ db.flip();
+ return db;
+ }
+}
diff --git a/src/main/java/duke/controllers/MainWindow.java b/src/main/java/duke/controllers/MainWindow.java
new file mode 100644
index 0000000000..26ad1c1844
--- /dev/null
+++ b/src/main/java/duke/controllers/MainWindow.java
@@ -0,0 +1,57 @@
+package duke.controllers;
+
+import duke.Duke;
+import duke.admin.Ui;
+import duke.exceptions.DukeException;
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TextField;
+import javafx.scene.image.Image;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.VBox;
+/**
+ * Controller for MainWindow. Provides the layout for the other controls.
+ */
+public class MainWindow extends AnchorPane {
+ @FXML
+ private ScrollPane scrollPane;
+ @FXML
+ private VBox dialogContainer;
+ @FXML
+ private TextField userInput;
+ @FXML
+ private Button sendButton;
+
+ private Duke duke;
+
+ private Image userImage = new Image(this.getClass().getResourceAsStream("/images/Aladdin.png"));
+ private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/Genie.png"));
+
+ @FXML
+ public void initialize() {
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+ }
+
+ public void setDuke(Duke d) {
+ duke = d;
+ }
+
+ /**
+ * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to
+ * the dialog container. Clears the user input after processing.
+ */
+ @FXML
+ private void handleUserInput() {
+ try {
+ String input = userInput.getText();
+ String response = duke.getResponse(input);
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialog(input, userImage),
+ DialogBox.getDukeDialog(response, dukeImage));
+ userInput.clear();
+ } catch (DukeException e) {
+ System.out.println(Ui.showErrorMessage(e.getMessage()));
+ }
+ }
+}
diff --git a/src/main/java/duke/data/duke.txt b/src/main/java/duke/data/duke.txt
new file mode 100644
index 0000000000..2f7a1de882
--- /dev/null
+++ b/src/main/java/duke/data/duke.txt
@@ -0,0 +1,2 @@
+E xxx 0 xxx Module Briefing /at 2022-02-21 23:00
+D xxx 0 xxx Module Bidding /by 2022-08-20 12:00
diff --git a/src/main/java/duke/exceptions/DukeException.java b/src/main/java/duke/exceptions/DukeException.java
new file mode 100644
index 0000000000..0c17b0e7ab
--- /dev/null
+++ b/src/main/java/duke/exceptions/DukeException.java
@@ -0,0 +1,18 @@
+package duke.exceptions;
+
+/**
+ * DukeException is an Exception unique to Duke.
+ */
+public class DukeException extends Exception {
+
+ public static final String INVALID_COMMAND = "Command was invalid or does not exist!";
+ public static final String INVALID_FORMAT = "Details input are invalid or in the wrong format!";
+
+ /**
+ * Constructs a DukeException instance with the inputted error message
+ * @param errorMessage message describing the fault
+ */
+ public DukeException(String errorMessage) {
+ super("What went wrong: " + errorMessage);
+ }
+}
diff --git a/src/main/java/duke/tasks/Deadline.java b/src/main/java/duke/tasks/Deadline.java
new file mode 100644
index 0000000000..ff4162a04c
--- /dev/null
+++ b/src/main/java/duke/tasks/Deadline.java
@@ -0,0 +1,122 @@
+package duke.tasks;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+import duke.exceptions.DukeException;
+
+/**
+ * Deadline is a Task that should be done by a certain date and time.
+ */
+public class Deadline extends Task {
+ private static String type = "D";
+ private String printedString;
+ private String descriptionWithoutDateTime;
+ private LocalDate deadlineDate;
+ private LocalTime deadlineTime;
+
+ /**
+ * Constructor for Deadline that takes in a description of the Deadline task and
+ * whether it is marked as done.
+ * @param description the description of the deadline task, containing the date
+ * and time of deadline
+ * @param isDone true if the deadline task has been marked as done
+ * @throws DukeException exception thrown when the deadline is invalid due to
+ * missing deadline or improper input format
+ */
+ public Deadline(String description, boolean isDone) throws DukeException {
+ this.isDone = isDone;
+ this.description = description;
+ try {
+ String[] splitDescription = description.split("/by ");
+ descriptionWithoutDateTime = splitDescription[0];
+ boolean containsDateTimeSpecification = splitDescription.length > 1;
+
+ if (containsDateTimeSpecification) {
+ String dateAndTime = splitDescription[1];
+ String[] splitDateTime = dateAndTime.split(" ");
+ boolean containsTimeSpecification = splitDateTime.length > 1;
+
+ if (containsTimeSpecification) {
+ this.deadlineDate = LocalDate.parse(splitDateTime[0]);
+ this.deadlineTime = LocalTime.parse(splitDateTime[1]);
+
+ updatePrintedString();
+ } else {
+ throw new DukeException(DukeException.INVALID_FORMAT);
+ }
+ } else {
+ throw new DukeException(DukeException.INVALID_FORMAT);
+ }
+ } catch (DateTimeParseException e) {
+ throw new DukeException(DukeException.INVALID_FORMAT);
+ }
+ }
+
+ /**
+ * Constructor for Deadline that takes in a description of the Deadline task
+ * @param description the description of the deadline task, containing the date
+ * and time of deadline
+ * @throws DukeException exception thrown when the deadline is invalid due to
+ * missing deadline or improper input format
+ */
+
+ public Deadline(String description) throws DukeException {
+ this(description, false);
+ }
+
+ private void updatePrintedString() {
+ assert descriptionWithoutDateTime != null;
+ assert deadlineDate != null;
+ assert deadlineTime != null;
+
+ this.printedString = descriptionWithoutDateTime + " (by: "
+ + this.deadlineDate.format(DateTimeFormatter.ofPattern("MMM dd yyyy")) + " "
+ + this.deadlineTime.format(DateTimeFormatter.ofPattern("hh:mma")) + ")";
+ }
+
+ @Override
+ public void updateDate(String dateString) throws DukeException {
+ try {
+ this.deadlineDate = LocalDate.parse(dateString);
+ updatePrintedString();
+ } catch (DateTimeParseException e) {
+ throw new DukeException("Unable to update deadline date due to improper input");
+ }
+ }
+
+ @Override
+ public void updateTime(String timeString) throws DukeException {
+ try {
+ this.deadlineTime = LocalTime.parse(timeString);
+ updatePrintedString();
+ } catch (DateTimeParseException e) {
+ throw new DukeException("Unable to update deadline time due to improper input");
+ }
+ }
+
+ @Override
+ public void updateDescription(String newDescription) {
+ this.descriptionWithoutDateTime = newDescription;
+ updatePrintedString();
+ }
+
+ /**
+ * Returns the String representation of deadline task.
+ * @return string representation of deadline task
+ */
+ @Override
+ public String toString() {
+ assert this.printedString != null;
+
+ return this.isDone ? "!!!DONE!!! Deadline - " + this.printedString
+ : "Deadline - " + this.printedString;
+ }
+
+ @Override
+ public String getType() {
+ return type;
+ }
+}
diff --git a/src/main/java/duke/tasks/Event.java b/src/main/java/duke/tasks/Event.java
new file mode 100644
index 0000000000..0e67e898bc
--- /dev/null
+++ b/src/main/java/duke/tasks/Event.java
@@ -0,0 +1,121 @@
+package duke.tasks;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+import duke.exceptions.DukeException;
+
+/**
+ * Event is a Task that should be attended at a certain date and time.
+ */
+public class Event extends Task {
+ private static String type = "E";
+ private String printedString;
+ private String descriptionWithoutDateTime;
+ private LocalDate eventDate;
+ private LocalTime eventTime;
+
+ /**
+ * Constructor for Event that takes in a description of the Event task and
+ * whether it is marked as done.
+ * @param description the description of the event task, containing the date and
+ * time of the event
+ * @param isDone true if the event task has been marked as done
+ * @throws DukeException exception thrown when the event datetime is invalid due
+ * to missing input or improper input format
+ */
+ public Event(String description, boolean isDone) throws DukeException {
+ this.description = description;
+ this.isDone = isDone;
+ try {
+ String[] splitDescription = description.split("/at ");
+ descriptionWithoutDateTime = splitDescription[0];
+ boolean containsDateTimeSpecification = splitDescription.length > 1;
+
+ if (containsDateTimeSpecification) {
+ String dateAndTime = splitDescription[1];
+ String[] splitDateTime = dateAndTime.split(" ");
+ boolean containsTimeSpecification = splitDateTime.length > 1;
+
+ if (containsTimeSpecification) {
+ this.eventDate = LocalDate.parse(splitDateTime[0]);
+ this.eventTime = LocalTime.parse(splitDateTime[1]);
+
+ updatePrintedString();
+ } else {
+ throw new DukeException(DukeException.INVALID_FORMAT);
+ }
+ } else {
+ throw new DukeException(DukeException.INVALID_FORMAT);
+ }
+ } catch (DateTimeParseException e) {
+ throw new DukeException(DukeException.INVALID_FORMAT);
+ }
+ }
+
+ /**
+ * Constructor for Event that takes in a description of the Event task.
+ * @param description the description of the event task, containing the date and
+ * time of the event
+ * @throws DukeException exception thrown when the event datetime is invalid due
+ * to missing input or improper input format
+ */
+ public Event(String description) throws DukeException {
+ this(description, false);
+ }
+
+ private void updatePrintedString() {
+ assert descriptionWithoutDateTime != null;
+ assert eventDate != null;
+ assert eventTime != null;
+
+ this.printedString = descriptionWithoutDateTime + " (at: "
+ + this.eventDate.format(DateTimeFormatter.ofPattern("MMM dd yyyy")) + " "
+ + this.eventTime.format(DateTimeFormatter.ofPattern("hh:mma")) + ")";
+ }
+
+ @Override
+ public void updateDate(String dateString) throws DukeException {
+ try {
+ this.eventDate = LocalDate.parse(dateString);
+ updatePrintedString();
+ } catch (DateTimeParseException e) {
+ throw new DukeException("Unable to update deadline date due to improper input");
+ }
+ }
+
+ @Override
+ public void updateTime(String timeString) throws DukeException {
+ try {
+ this.eventTime = LocalTime.parse(timeString);
+ updatePrintedString();
+ } catch (DateTimeParseException e) {
+ throw new DukeException("Unable to update deadline time due to improper input");
+ }
+ }
+
+ @Override
+ public void updateDescription(String newDescription) {
+ this.descriptionWithoutDateTime = newDescription;
+ updatePrintedString();
+ }
+
+ /**
+ * Returns the String representation of event task.
+ * @return string representation of event task
+ */
+ @Override
+ public String toString() {
+ assert this.printedString != null;
+ return this.isDone ? "!!!DONE!!! Event - " + this.printedString
+ : "Event - " + this.printedString;
+ }
+
+ @Override
+ public String getType() {
+ return type;
+ }
+
+}
diff --git a/src/main/java/duke/tasks/Task.java b/src/main/java/duke/tasks/Task.java
new file mode 100644
index 0000000000..b8b3347375
--- /dev/null
+++ b/src/main/java/duke/tasks/Task.java
@@ -0,0 +1,37 @@
+package duke.tasks;
+
+import duke.exceptions.DukeException;
+
+/**
+ * Task is a class that manages the actions that a Task can do such as marking
+ * itself as done etc.
+ */
+public abstract class Task {
+ protected String type;
+ protected String description;
+ protected boolean isDone;
+
+ /**
+ * Toggles the isDone value.
+ */
+ public void toggleMark() {
+ this.isDone = !(this.isDone);
+ }
+
+ /**
+ * Returns a boolean value depending on whether the task is done,
+ * if it is then the method returns true.
+ * @return true if the task has been marked as done
+ */
+ public boolean isDone() {
+ return isDone;
+ }
+
+ public abstract String getType();
+
+ public abstract void updateDescription(String description);
+
+ public abstract void updateDate(String newDate) throws DukeException;
+
+ public abstract void updateTime(String newTime) throws DukeException;
+}
diff --git a/src/main/java/duke/tasks/ToDo.java b/src/main/java/duke/tasks/ToDo.java
new file mode 100644
index 0000000000..2c9a988498
--- /dev/null
+++ b/src/main/java/duke/tasks/ToDo.java
@@ -0,0 +1,60 @@
+package duke.tasks;
+
+import duke.exceptions.DukeException;
+
+/**
+ * ToDo is a Task with no specific start or end date or time.
+ */
+public class ToDo extends Task {
+ private static String type = "T";
+
+ /**
+ * Constructor for ToDo task takes in the description of the task and whether it
+ * has been marked as done.
+ * @param description the description of the ToDo task
+ * @param isDone true if the task has been marked as done
+ */
+ public ToDo(String description, boolean isDone) {
+ this.description = description;
+ this.isDone = isDone;
+ }
+
+ /**
+ * Constructor for ToDo task takes in the description of the task.
+ * @param description the description of the ToDo task
+ */
+ public ToDo(String description) {
+ this(description, false);
+ }
+
+ /**
+ * Returns the String representation of todo task.
+ * @return string representation of todo task
+ */
+ @Override
+ public String toString() {
+ assert this.description != null;
+ return this.isDone ? "!!!DONE!!! To do - " + this.description
+ : "To do - " + this.description;
+ }
+
+ @Override
+ public void updateDate(String newDate) throws DukeException {
+ throw new DukeException("No date value for Todo task");
+ }
+
+ @Override
+ public void updateTime(String newTime) throws DukeException {
+ throw new DukeException("No time value for Todo task");
+ }
+
+ @Override
+ public String getType() {
+ return type;
+ }
+
+ @Override
+ public void updateDescription(String description) {
+ this.description = description;
+ }
+}
diff --git a/src/main/java/duke/tasks/Trigger.java b/src/main/java/duke/tasks/Trigger.java
new file mode 100644
index 0000000000..ad2889710a
--- /dev/null
+++ b/src/main/java/duke/tasks/Trigger.java
@@ -0,0 +1,59 @@
+package duke.tasks;
+
+import duke.exceptions.DukeException;
+
+/**
+ * Trigger is a Task to trigger a message.
+ */
+public class Trigger extends Task {
+ private static String type = "TRG";
+
+ /**
+ * Constructor for Trigger task takes in the description of the task and whether it
+ * has been marked as done.
+ * @param description the description of the Trigger task
+ * @param isDone true if the task has been marked as done
+ */
+ public Trigger(String description, boolean isDone) {
+ this.description = description;
+ this.isDone = isDone;
+ }
+
+ /**
+ * Constructor for Trigger task takes in the description of the task.
+ * @param description the description of the Trigger task
+ */
+ public Trigger(String description) {
+ this(description, false);
+ }
+
+ /**
+ * Returns the String representation of todo task.
+ * @return string representation of todo task
+ */
+ @Override
+ public String toString() {
+ assert this.description != null;
+ return this.description;
+ }
+
+ @Override
+ public void updateDate(String newDate) throws DukeException {
+ System.out.println("No date value for Todo task");
+ }
+
+ @Override
+ public void updateTime(String newTime) throws DukeException {
+ System.out.println("No time value for Todo task");
+ }
+
+ @Override
+ public String getType() {
+ return type;
+ }
+
+ @Override
+ public void updateDescription(String description) {
+ this.description = description;
+ }
+}
diff --git a/src/main/resources/images/Aladdin.png b/src/main/resources/images/Aladdin.png
new file mode 100644
index 0000000000..60add44011
Binary files /dev/null and b/src/main/resources/images/Aladdin.png differ
diff --git a/src/main/resources/images/Genie.png b/src/main/resources/images/Genie.png
new file mode 100644
index 0000000000..b8bfac6325
Binary files /dev/null and b/src/main/resources/images/Genie.png differ
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 0000000000..b0ca251c38
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
new file mode 100644
index 0000000000..69fbe766e3
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/duke/admin/TaskListTest.java b/src/test/java/duke/admin/TaskListTest.java
new file mode 100644
index 0000000000..a6a48741e8
--- /dev/null
+++ b/src/test/java/duke/admin/TaskListTest.java
@@ -0,0 +1,17 @@
+package duke.admin;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class TaskListTest {
+ private TaskList test = new TaskList();
+
+ /**
+ * Tests the getNumberOfTasks method in Task list.
+ */
+ @Test
+ public void taskCountTest() {
+ assertEquals(0, test.getNumberOfTasks(), "Failed");
+ }
+}
diff --git a/src/test/java/duke/tasks/TaskTest.java b/src/test/java/duke/tasks/TaskTest.java
new file mode 100644
index 0000000000..492f4bfe06
--- /dev/null
+++ b/src/test/java/duke/tasks/TaskTest.java
@@ -0,0 +1,22 @@
+package duke.tasks;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class TaskTest {
+ private Task test = new ToDo("Test", false);
+
+ /**
+ * Tests the toggleMark method adopted by all Tasks subclasses
+ */
+ @Test
+ public void toggleMarkTest() {
+
+ assertEquals("To do - Test", test.toString(), "Failed creation");
+
+ test.toggleMark();
+
+ assertEquals("!!!DONE!!! To do - Test", test.toString(), "Failed toggle");
+ }
+}
diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat
index 0873744649..97c1d9157a 100644
--- a/text-ui-test/runtest.bat
+++ b/text-ui-test/runtest.bat
@@ -7,7 +7,7 @@ REM delete output from previous run
if exist ACTUAL.TXT del ACTUAL.TXT
REM compile the code into the bin folder
-javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\*.java
+javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\Duke.java
IF ERRORLEVEL 1 (
echo ********** BUILD FAILURE **********
exit /b 1
diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh
old mode 100644
new mode 100755