diff --git a/README.md b/README.md
index 0dd8f7fb21..28d87153fb 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
-# Duke project template
+# Duke Project
-This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it.
+Say hello to Duke! It is a personal assistant chatbot to help you manage your tasks.\
+Given below are instructions on how to use it so give it a go!
## Setting up in Intellij
@@ -12,12 +13,20 @@ Prerequisites: JDK 11, update Intellij to the most recent version.
1. Select the project directory, and click `OK`.
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).
-1. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below:
- ```
- Hello from
- ____ _
- | _ \ _ _| | _____
- | | | | | | | |/ / _ \
- | |_| | |_| | < __/
- |____/ \__,_|_|\_\___|
- ```
+1. After that, locate the `src/main/java/duke/launcher.java` file, right-click it, and choose `Run Launcher.main()`. There should be a popup for you to interact with Duke.
+
+## Commands
+
+Here are some of the ways for Duke to understand you:
+
+Action | Command Format | Examples
+------------ | ------------- | -------------
+To add a todo task | `todo` | e.g. `todo homework`
+To add a deadline task | `deadline` | e.g. `deadline project /by 2020-01-01 23:59`
+To add an event task | `event` | e.g. `event party /at 2020-01-01 18:00`
+To list all current tasks | `list` | e.g. `list`
+To delete a task | `delete` | e.g. `delete 2` will delete the 2nd task in the list
+To mark a task as done | `done` | e.g. `done 3` will mark the 3rd task in the list as done
+To find tasks with a
specific keyword | `find` | e.g. `find book` will list all existing tasks with the
word `book`
+To list tasks with a
specific date | `taskdate` | e.g. `taskdate 2020-01-01` will list all existing tasks
with the date `2020-01-01`
+To exit | `bye` | e.g. `bye`
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..aedbcd1941
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,64 @@
+plugins {
+ id 'java'
+ id 'application'
+ id 'checkstyle'
+ id 'com.github.johnrengelman.shadow' version '5.1.0'
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0'
+ testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0'
+
+ String javaFxVersion = '11'
+
+ implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux'
+ implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux'
+ implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux'
+ 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'
+}
+
+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 {
+ toolVersion = '8.29'
+}
+
+run {
+ enableAssertions = true
+ standardInput = System.in
+}
+
+
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000000..7951f3a6c4
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,398 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
new file mode 100644
index 0000000000..6423654db1
--- /dev/null
+++ b/config/checkstyle/suppressions.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/data/duke.txt b/data/duke.txt
new file mode 100644
index 0000000000..efa3aad193
--- /dev/null
+++ b/data/duke.txt
@@ -0,0 +1,8 @@
+[D][X] project (by: Mar 3 2020, 7:00PM)
+[E][X] party (at: Mar 3 2020, 8:00PM)
+[T][ ] book review
+[E][ ] book release (at: Jan 1 2021, 6:00PM)
+[T][X] clean room
+[T][X] a
+[D][ ] b (by: Mar 4 2020, 7:00PM)
+[E][ ] c (at: Sep 9 2020, 7:00PM)
diff --git a/docs/README.md b/docs/README.md
index fd44069597..405207a4fa 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -2,19 +2,149 @@
## Features
-### Feature 1
-Description of feature.
+### Adding a todo task: `todo`
-## Usage
+Add a todo task.
-### `Keyword` - Describe action
+Example of usage:
+`todo homework`
-Describe action and its outcome.
+Expected outcome:
+```
+Got it. I've added this task:
+ [T][ ] homework
+Now you have 1 task in the list.
+````
+
+
+### Adding a deadline task: `deadline`
+
+Add a deadline task.
+
+Example of usage:
+`deadline project /by 2020-01-01 23:39`
+
+Expected outcome:
+```
+Got it. I've added this task:
+ [D][ ] project (by: Jan 1 2020, 11:59PM)
+Now you have 2 tasks in the list.
+````
+
+
+### Adding an event task: `event`
+
+Add an event task.
+
+Example of usage:
+`event party /at 2020-01-01 18:00`
+
+Expected outcome:
+```
+Got it. I've added this task:
+ [E][ ] party (at: Jan 01 2020, 6:00PM)
+Now you have 3 tasks in the list.
+````
+
+
+### Listing all tasks: `list`
+
+List all existing tasks.
Example of usage:
+`list`
+
+Expected outcome:
+```
+Here are the tasks in your list:
+1.[T][ ] homework
+2.[D][ ] project (by: Jan 1 2020, 11:59PM)
+3.[E][ ] party (at: Jan 01 2020, 6:00PM)
+```
+
-`keyword (optional arguments)`
+### Deleting a task: `delete`
+
+Delete a specified task.
+
+Example of usage: `delete 2`
Expected outcome:
+```
+Noted. I've removed this task:
+ [D][ ] project (by: Jan 1 2020, 11:59PM)
+Now you have 2 tasks in the list.
+```
+
+
+### Marking a task as done: `done`
+
+Mark a specified task as done.
+
+Example of usage:
+`done 1`
+
+Expected outcome:
+```
+Nice! I've marked this task as done:
+[T][X] homework
+```
+
+
+### Finding tasks with a specified keyword: `find`
+
+Find tasks that contain a specified keyword.
+
+Example of usage:
+`find party`
+
+Expected outcome:
+```
+Here are the matching tasks in your list:
+1.[E][ ] party (at: Jan 01 2020, 6:00PM)
+```
+
+
+### Listing tasks with a specified date: `taskdate`
+
+Listings tasks that have a specified date.
+
+Example of usage:
+`taskdate 2020-01-01`
+
+Expected outcome:
+```
+Here are the tasks on Jan 1 2020 in your list:
+1.[D][ ] project (by: Jan 1 2020, 11:59PM)
+2.[E][ ] party (at: Jan 01 2020, 6:00PM)
+```
+
+
+### Exiting the program: `bye`
+
+Exit the program.
+
+Example of usage:
+`bye`
+
+Expected outcome:
+```
+Bye. Hope to see you again soon!
+```
+
+
+
+
+## Command Summary
-`outcome`
+Command Format | Examples
+------------- | -------------
+`todo` | e.g. `todo homework`
+`deadline` | e.g. `deadline project /by 2020-01-01 23:59`
+`event` | e.g. `event party /at 2020-01-01 18:00`
+`list` | e.g. `list`
+`delete` | e.g. `delete 2` will delete the 2nd task in the list
+`done` | e.g. `done 3` will mark the 3rd task in the list as done
+`find` | e.g. `find book` will list all existing tasks with the word `book`
+`taskdate` | e.g. `taskdate 2020-01-01` will list all existing tasks with the date `2020-01-01`
+`bye` | e.g. `bye`
\ No newline at end of file
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..5b0935055b
Binary files /dev/null and b/docs/Ui.png differ
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..f3d88b1c2f
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..b7c8c5dbf5
--- /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-6.2-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000000..2fe81a7d95
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,183 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or 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 UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$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 "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# 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
+ ;;
+ 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000000..62bd9b9cce
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,103 @@
+@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 init
+
+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 init
+
+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
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+: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 %CMD_LINE_ARGS%
+
+: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/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..6e864153e8
--- /dev/null
+++ b/src/main/java/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: duke.Duke
+
diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java
new file mode 100644
index 0000000000..7f1be8a002
--- /dev/null
+++ b/src/main/java/duke/Duke.java
@@ -0,0 +1,52 @@
+package duke;
+
+import java.io.IOException;
+import java.time.format.DateTimeParseException;
+
+import duke.command.Command;
+import duke.exception.DukeException;
+import duke.ui.Ui;
+import duke.task.TaskList;
+
+/**
+ * Represents a task manager that allows users to add, delete and mark tasks as done.
+ */
+public class Duke {
+
+ private Storage storage;
+ private TaskList tasks;
+ private Ui ui;
+
+ /**
+ * Creates a new instance of Duke with given filePath.
+ *
+ * @param filePath
+ */
+ public Duke(String filePath) {
+ ui = new Ui();
+ storage = new Storage(filePath);
+ try {
+ tasks = new TaskList(storage.load());
+ } catch (DukeException | IOException e) {
+ ui.showError(e.getMessage());
+ tasks = new TaskList();
+ }
+ }
+
+
+ /**
+ * Gets response from duke to be printed to user.
+ *
+ * @param input User input.
+ * @return Output string.
+ */
+ public String getResponse(String input) {
+ try {
+ Command c = Parser.parse(input);
+ return c.execute(tasks, ui, storage);
+ } catch (DukeException | IOException | DateTimeParseException e) {
+ return e.getMessage();
+ }
+ }
+
+}
diff --git a/src/main/java/duke/FileManager.java b/src/main/java/duke/FileManager.java
new file mode 100644
index 0000000000..514593ebc9
--- /dev/null
+++ b/src/main/java/duke/FileManager.java
@@ -0,0 +1,107 @@
+package duke;
+
+import duke.task.TaskList;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Scanner;
+
+/**
+ * Represents a file manager.
+ */
+public class FileManager {
+
+ /**
+ * Checks if the given filePath exists.
+ *
+ * @param filePath Path of file.
+ * @return True if file exists and false if file does not exist.
+ */
+ public static boolean doesExist(String filePath) {
+ Path path = Path.of(filePath);
+ return Files.exists(path);
+ }
+
+ /**
+ * Prints the contents of a file.
+ *
+ * @param filePath Path of file.
+ * @throws FileNotFoundException If file is not found.
+ */
+ public static void printFileContents(String filePath) throws FileNotFoundException {
+
+ File f = new File(filePath);
+ Scanner s = new Scanner(f);
+
+ while (s.hasNext()) {
+ System.out.println(s.nextLine());
+ }
+ }
+
+ /**
+ * Create and writes to a new file.
+ *
+ * @param filePath Path of file.
+ * @param textToAdd Text to write into file.
+ * @throws IOException If there are input or output errors.
+ */
+ public static void writeToNewFile(String filePath, String textToAdd) throws IOException {
+ FileWriter fw = new FileWriter(filePath);
+ fw.write(textToAdd);
+ fw.close();
+ }
+
+ /**
+ * Delete the file at the given file path.
+ *
+ * @param filePath Path of file.
+ */
+ public static void deleteFile(String filePath) {
+ File f = new File(filePath);
+ f.delete();
+ }
+
+ /**
+ * Deletes a specific line from a file.
+ *
+ * @param filePath Path of file.
+ * @param lineNumber Number of line to be deleted (1-based indexing).
+ * @throws IOException If there are input or output errors.
+ */
+ public static void deleteLine(String filePath, int lineNumber, Storage storage) throws IOException {
+ File inputFile = new File(filePath);
+ File outputFile = new File("data/newDuke.txt");
+ FileManager.writeToNewFile("data/newDuke.txt", "");
+
+ Scanner sc = new Scanner(inputFile);
+ int counter = 0;
+ while (sc.hasNextLine()) {
+ String nextLine = sc.nextLine();
+ counter++;
+ if (counter != lineNumber) {
+ storage.appendToFile("data/newDuke.txt", nextLine);
+ }
+ }
+
+ outputFile.renameTo(inputFile);
+ }
+
+ /**
+ * Updates the list of tasks with no header and number labels.
+ *
+ * @param filePath File path.
+ * @param tasks Task list.
+ * @throws IOException If file does not exist.
+ */
+ public static void updateTaskList(String filePath, TaskList tasks) throws IOException {
+ File inputFile = new File(filePath);
+ File outputFile = new File("data/newDuke.txt");
+ FileManager.writeToNewFile("data/newDuke.txt", tasks.printTaskListWithoutNumbers());
+ outputFile.renameTo(inputFile);
+ }
+
+}
diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java
new file mode 100644
index 0000000000..5483e63637
--- /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);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/Main.java b/src/main/java/duke/Main.java
new file mode 100644
index 0000000000..d9f6b23d08
--- /dev/null
+++ b/src/main/java/duke/Main.java
@@ -0,0 +1,36 @@
+package duke;
+
+import java.io.IOException;
+
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.layout.AnchorPane;
+import javafx.stage.Stage;
+
+import duke.ui.MainWindow;
+
+/**
+ * A GUI for Duke using FXML.
+ */
+public class Main extends Application {
+
+ private Duke duke = new Duke("data/duke.txt");
+
+ @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);
+ stage.setTitle("Duke");
+ fxmlLoader.getController().setDuke(duke);
+ stage.show();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/duke/Parser.java b/src/main/java/duke/Parser.java
new file mode 100644
index 0000000000..ef01680b77
--- /dev/null
+++ b/src/main/java/duke/Parser.java
@@ -0,0 +1,64 @@
+package duke;
+
+import duke.command.AddCommand;
+import duke.command.Command;
+import duke.command.DeleteCommand;
+import duke.command.DoneCommand;
+import duke.command.ExitCommand;
+import duke.command.FindCommand;
+import duke.command.ListCommand;
+import duke.command.TaskdateCommand;
+import duke.exception.DukeException;
+
+/**
+ * Represents a parser that makes sense of user's commands.
+ */
+public class Parser {
+
+ /**
+ * Parses through user command and calls the relevant command.
+ *
+ * @param fullCommand User command.
+ * @return Relevant command.
+ * @throws DukeException If user input is not in the correct format or is invalid.
+ */
+ public static Command parse(String fullCommand) throws DukeException {
+ String type = fullCommand.split(" ")[0];
+
+ String description;
+
+ if (type.equals("list")) {
+ return new ListCommand();
+ } else if (type.equals("bye")) {
+ return new ExitCommand();
+ } else if (type.equals("todo") || type.equals("deadline") || type.equals("event")) {
+ if (fullCommand.split(" ").length == 1) {
+ String aOrAn = (type.equals("event")) ? "an " : "a ";
+ throw new DukeException("☹ OOPS!!! The description of " + aOrAn + type + " cannot be empty.");
+ }
+ description = fullCommand.split(type + " ")[1];
+ return new AddCommand(type, description);
+ } else if (type.equals("delete")) {
+ if (fullCommand.split(" ").length == 1) {
+ throw new DukeException("☹ OOPS!!! The task number has not been specified.");
+ }
+ description = fullCommand.split(type + " ")[1];
+ return new DeleteCommand(description);
+ } else if (type.equals("done")) {
+ if (fullCommand.split(" ").length == 1) {
+ throw new DukeException("☹ OOPS!!! The task number has not been specified.");
+ }
+ description = fullCommand.split(type + " ")[1];
+ return new DoneCommand(description);
+ } else if (type.equals("taskdate")) {
+ description = fullCommand.split(type + " ")[1];
+ return new TaskdateCommand(description);
+ } else if (type.equals("find")) {
+ description = fullCommand.split(type + " ")[1];
+ return new FindCommand(description);
+ } else {
+ throw new DukeException("☹ OOPS!!! I'm sorry, but I don't know what that means :-(");
+ }
+ }
+}
+
diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/Storage.java
new file mode 100644
index 0000000000..d80ac35cdb
--- /dev/null
+++ b/src/main/java/duke/Storage.java
@@ -0,0 +1,211 @@
+package duke;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Scanner;
+
+import duke.exception.DukeException;
+import duke.task.TaskList;
+import duke.task.TaskType;
+
+/**
+ * Represents file storage & deals with loading tasks from the file and saving tasks in the file
+ */
+public class Storage {
+
+ protected String filePath;
+
+ /**
+ * Creates a new instance of Storage at the specified file path.
+ *
+ * @param filePath
+ */
+ public Storage(String filePath) {
+ this.filePath = filePath;
+ }
+
+
+ public String getFilePath() {
+ return this.filePath;
+ }
+
+ /**
+ * Loads the file at the given file path.
+ *
+ * @return Task list of existing tasks in file.
+ * @throws DukeException If file path does not exist.
+ * @throws IOException If there are input or output issues.
+ */
+ public TaskList load() throws DukeException, IOException {
+ Path path = Path.of(this.filePath);
+ if (Files.notExists(path)) {
+ this.createFile();
+ throw new DukeException("File path does not exist! "
+ + "A new file has been created according to given file path.");
+ } else {
+ return this.convertTextFileToTaskList();
+ }
+ }
+
+ /**
+ * Creates a new folder and new file at the given file path.
+ *
+ * @throws IOException
+ */
+ public void createFile() throws IOException {
+ String folderPath = this.filePath.substring(0, this.filePath.lastIndexOf("/") + 1);
+ File folder = new File(folderPath);
+ folder.mkdirs();
+
+ File file = new File(filePath);
+ file.createNewFile();
+ }
+
+ /**
+ * Append text to existing file.
+ *
+ * @param filePath Path of file.
+ * @param textToAppend Text to append to existing file.
+ * @throws IOException If there are input or output errors.
+ */
+ public void appendToFile(String filePath, String textToAppend) throws IOException {
+ Path path = Path.of(filePath);
+ if (Files.notExists(path)) {
+ this.createFile();
+ }
+ FileWriter fw = new FileWriter(filePath, true);
+ fw.write(textToAppend + "\n");
+ fw.close();
+ }
+
+ /**
+ * Reads text file and returns task list with existing tasks in text file.
+ *
+ * @return Task list of existing tasks in file.
+ * @throws IOException If there are input or output errors.
+ * @throws DukeException If description of task is not in the correct format.
+ */
+ public TaskList convertTextFileToTaskList() throws IOException, DukeException {
+ File f = new File(this.filePath);
+ Scanner sc = new Scanner(f);
+ TaskList taskList = new TaskList();
+
+ while (sc.hasNextLine()) {
+ String nextLine = sc.nextLine();
+ String taskTypeLetter = Character.toString(nextLine.charAt(1));
+ boolean isDone = (Character.toString(nextLine.charAt(4)).equals("X"));
+ if (taskTypeLetter.equals("T")) {
+ taskList.addTask(TaskType.TODO, nextLine.substring(7), isDone,true, this);
+ } else if (taskTypeLetter.equals("D")) {
+ String[] nextLineArr = nextLine.substring(7).split(" \\(by: ");
+ String[] dateTime = nextLineArr[1].substring(0, nextLineArr[1].lastIndexOf(")")).split(", ");
+ String date = dateTime[0];
+ String time = dateTime[1];
+ String description = nextLineArr[0] + " /by "
+ + Storage.convertOutputDateToInputDate(date) + " "
+ + Storage.convertOutputTimeToInputTime(time);
+ taskList.addTask(TaskType.DEADLINE, description, isDone,true, this);
+ } else if (taskTypeLetter.equals("E")) {
+ String[] nextLineArr = nextLine.substring(7).split(" \\(at: ");
+ String[] dateTime = nextLineArr[1].substring(0, nextLineArr[1].lastIndexOf(")")).split(", ");
+ String date = dateTime[0];
+ String time = dateTime[1];
+ String description = nextLineArr[0] + " /at "
+ + Storage.convertOutputDateToInputDate(date) + " "
+ + Storage.convertOutputTimeToInputTime(time);
+ taskList.addTask(TaskType.EVENT, description, isDone,true, this);
+ } else {
+ assert false : "Task type is invalid.";
+ }
+ }
+
+ return taskList;
+ }
+
+ /**
+ * Converts output date format into a format that can be parsed into LocalDate.
+ *
+ * @param outputDate Output date.
+ * @return Date in a format that can be parsed into LocalDate
+ */
+ public static String convertOutputDateToInputDate(String outputDate) {
+ String[] outputDateArr = outputDate.split(" ");
+ String year = outputDateArr[2];
+ String month = "";
+ if (outputDateArr[0].equals("Jan")) {
+ month = "01";
+ } else if (outputDateArr[0].equals("Feb")) {
+ month = "02";
+ } else if (outputDateArr[0].equals("Mar")) {
+ month = "03";
+ } else if (outputDateArr[0].equals("Apr")) {
+ month = "04";
+ } else if (outputDateArr[0].equals("May")) {
+ month = "05";
+ } else if (outputDateArr[0].equals("Jun")) {
+ month = "06";
+ } else if (outputDateArr[0].equals("Jul")) {
+ month = "07";
+ } else if (outputDateArr[0].equals("Aug")) {
+ month = "08";
+ } else if (outputDateArr[0].equals("Sep")) {
+ month = "09";
+ } else if (outputDateArr[0].equals("Oct")) {
+ month = "10";
+ } else if (outputDateArr[0].equals("Nov")) {
+ month = "11";
+ } else if (outputDateArr[0].equals("Dec")) {
+ month = "12";
+ } else {
+ assert false : "The month of the output date is invalid.";
+ }
+ String day = String.format("%02d", Integer.parseInt(outputDateArr[1]));
+ return year + "-" + month + "-" + day;
+ }
+
+ /**
+ * Converts output time format into a format that can be parsed into LocalTime.
+ *
+ * @param outputTime Output time.
+ * @return Time in a format that can be parsed into LocalTime
+ */
+ public static String convertOutputTimeToInputTime(String outputTime) {
+ String amOrPm = outputTime.substring(outputTime.length() - 2);
+ String time = outputTime.substring(0, outputTime.length() - 2);
+ String[] timeArr = time.split(":");
+ int additionalHours = 0;
+ if (amOrPm.equals("PM")) {
+ additionalHours = 12;
+ }
+
+ String hour = String.format("%02d", Integer.parseInt(timeArr[0]) + additionalHours);
+ return hour + ":" + timeArr[1];
+ }
+
+ /**
+ * Checks if new task to be added already exists in current task list.
+ *
+ * @param newTask New task to be added.
+ * @throws FileNotFoundException If file is not found at the given file path.
+ * @throws DukeException If new task to be added already exists in current task list.
+ */
+ public void checkForDuplicate(String newTask) throws FileNotFoundException, DukeException {
+ File file = new File(this.filePath);
+ Scanner sc = new Scanner(file);
+
+ while (sc.hasNextLine()) {
+ String existingTask = sc.nextLine();
+ String existingTaskNotDone = existingTask.substring(0, 4) + " " + existingTask.substring(5);
+ if (existingTaskNotDone.equals(newTask)) {
+ throw new DukeException("This task already exists in your list! This task will not be added.");
+ } else {
+ continue;
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/duke/command/AddCommand.java b/src/main/java/duke/command/AddCommand.java
new file mode 100644
index 0000000000..e7b72a8adc
--- /dev/null
+++ b/src/main/java/duke/command/AddCommand.java
@@ -0,0 +1,47 @@
+package duke.command;
+
+import java.io.IOException;
+
+import duke.Storage;
+import duke.ui.Ui;
+import duke.exception.DukeException;
+import duke.task.TaskList;
+import duke.task.TaskType;
+
+/**
+ * Represents a command to add a task.
+ */
+public class AddCommand extends Command {
+
+ /**
+ * Creates a new instance of AddCommand.
+ *
+ * @param type Type of add command (todo, deadline, event).
+ * @param description Description of task.
+ */
+ public AddCommand(String type, String description) {
+ this.type = type;
+ this.description = description;
+ this.isExit = false;
+ }
+
+ /**
+ * Adds todo, deadline or event tasks.
+ *
+ * @param tasks Task list.
+ * @param ui User interface.
+ * @param storage Storage.
+ * @return Output string.
+ * @throws IOException If there are any input or output issues.
+ * @throws DukeException If user input is not in the correct format or is invalid.
+ */
+ @Override
+ public String execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DukeException {
+ return tasks.addTask(TaskType.valueOf(this.type.toUpperCase()), description, false, false, storage);
+ }
+
+ @Override
+ public boolean isExit() {
+ return this.isExit;
+ }
+}
diff --git a/src/main/java/duke/command/Command.java b/src/main/java/duke/command/Command.java
new file mode 100644
index 0000000000..4acf0f3862
--- /dev/null
+++ b/src/main/java/duke/command/Command.java
@@ -0,0 +1,22 @@
+package duke.command;
+
+import java.io.IOException;
+
+import duke.Storage;
+import duke.ui.Ui;
+import duke.exception.DukeException;
+import duke.task.TaskList;
+
+/**
+ * Represents a command called by the user.
+ */
+public abstract class Command {
+
+ protected String type;
+ protected String description;
+ protected boolean isExit;
+
+ public abstract String execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DukeException;
+
+ public abstract boolean isExit();
+}
diff --git a/src/main/java/duke/command/DeleteCommand.java b/src/main/java/duke/command/DeleteCommand.java
new file mode 100644
index 0000000000..e40f85baa4
--- /dev/null
+++ b/src/main/java/duke/command/DeleteCommand.java
@@ -0,0 +1,45 @@
+package duke.command;
+
+import java.io.IOException;
+
+import duke.Storage;
+import duke.ui.Ui;
+import duke.exception.DukeException;
+import duke.task.TaskList;
+
+/**
+ * Represents a command to delete a task.
+ */
+public class DeleteCommand extends Command {
+
+ /**
+ * Creates a new instance of DeleteCommand.
+ *
+ * @param description Task number to delete.
+ */
+ public DeleteCommand(String description) {
+ this.type = "delete";
+ this.description = description;
+ this.isExit = false;
+ }
+
+ /**
+ * Deletes specified task.
+ *
+ * @param tasks Task list.
+ * @param ui UI.
+ * @param storage Storage.
+ * @return Output string.
+ * @throws IOException If there are input or output issues.
+ * @throws DukeException If no task number was specified or task number specified is invalid.
+ */
+ @Override
+ public String execute(TaskList tasks, Ui ui, Storage storage) throws IOException, DukeException {
+ return tasks.deleteTask(Integer.parseInt(description), storage);
+ }
+
+ @Override
+ public boolean isExit() {
+ return this.isExit;
+ }
+}
diff --git a/src/main/java/duke/command/DoneCommand.java b/src/main/java/duke/command/DoneCommand.java
new file mode 100644
index 0000000000..213a2be0f2
--- /dev/null
+++ b/src/main/java/duke/command/DoneCommand.java
@@ -0,0 +1,53 @@
+package duke.command;
+
+import duke.FileManager;
+import duke.Storage;
+import duke.ui.Ui;
+import duke.exception.DukeException;
+import duke.task.TaskList;
+
+import java.io.IOException;
+
+/**
+ * Represents a command to mark a task as done.
+ */
+public class DoneCommand extends Command {
+
+ /**
+ * Creates a new instance of DoneCommand.
+ *
+ * @param description Task number to be marked as done.
+ */
+ public DoneCommand(String description) {
+ this.type = "done";
+ this.description = description;
+ this.isExit = false;
+ }
+
+ /**
+ * Marks tasks as combine.
+ *
+ * @param tasks Task list.
+ * @param ui User interface.
+ * @param storage Storage.
+ * @return Output string.
+ * @throws DukeException If task number does not exist or is not specified.
+ */
+ @Override
+ public String execute(TaskList tasks, Ui ui, Storage storage) throws DukeException, IOException {
+ if (Integer.parseInt(this.description) > tasks.getTaskListSize() || Integer.parseInt(this.description) == 0) {
+ throw new DukeException("☹ OOPS!!! This task number does not exist.");
+ }
+
+ int taskNo = Integer.parseInt(this.description);
+ assert (taskNo > 0 && taskNo < tasks.getTaskListSize()) : "Invalid task number to be marked as done.";
+ String doneTask = tasks.getTask(taskNo).markAsDone();
+ FileManager.updateTaskList(storage.getFilePath(), tasks);
+ return doneTask;
+ }
+
+ @Override
+ public boolean isExit() {
+ return this.isExit;
+ }
+}
diff --git a/src/main/java/duke/command/ExitCommand.java b/src/main/java/duke/command/ExitCommand.java
new file mode 100644
index 0000000000..c4b5e12ac2
--- /dev/null
+++ b/src/main/java/duke/command/ExitCommand.java
@@ -0,0 +1,39 @@
+package duke.command;
+
+import duke.Storage;
+import duke.ui.Ui;
+import duke.task.TaskList;
+
+/**
+ * Represents a command to exit Duke.
+ */
+public class ExitCommand extends Command {
+
+ /**
+ * Creates a new instance of ExitCommand.
+ * isExit is set to true.
+ */
+ public ExitCommand() {
+ this.type = "bye";
+ this.description = "";
+ this.isExit = true;
+ }
+
+ /**
+ * Terminates Duke.
+ *
+ * @param tasks Task List,
+ * @param ui User interface.
+ * @param storage Storage.
+ * @return Output string.
+ */
+ @Override
+ public String execute(TaskList tasks, Ui ui, Storage storage) {
+ return ui.showExit();
+ }
+
+ @Override
+ public boolean isExit() {
+ return this.isExit;
+ }
+}
diff --git a/src/main/java/duke/command/FindCommand.java b/src/main/java/duke/command/FindCommand.java
new file mode 100644
index 0000000000..9f95296f21
--- /dev/null
+++ b/src/main/java/duke/command/FindCommand.java
@@ -0,0 +1,40 @@
+package duke.command;
+
+import duke.Storage;
+import duke.ui.Ui;
+import duke.task.TaskList;
+
+/**
+ * Represents a command to find tasks by searching a keyword.
+ */
+public class FindCommand extends Command {
+
+ /**
+ * Creates a new instance of FindCommand.
+ *
+ * @param description Keyword to be matched to current tasks.
+ */
+ public FindCommand(String description) {
+ this.type = "find";
+ this.description = description;
+ this.isExit = false;
+ }
+
+ /**
+ * Prints tasks with the matching keyword.
+ *
+ * @param tasks Task list.
+ * @param ui User interface.
+ * @param storage Storage.
+ * @return Output string.
+ */
+ @Override
+ public String execute(TaskList tasks, Ui ui, Storage storage) {
+ return tasks.printTasksWith(description);
+ }
+
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/duke/command/ListCommand.java b/src/main/java/duke/command/ListCommand.java
new file mode 100644
index 0000000000..207e750e45
--- /dev/null
+++ b/src/main/java/duke/command/ListCommand.java
@@ -0,0 +1,38 @@
+package duke.command;
+
+import duke.Storage;
+import duke.ui.Ui;
+import duke.task.TaskList;
+
+/**
+ * Represents a command to list all current tasks.
+ */
+public class ListCommand extends Command {
+
+ /**
+ * Creates a new instance of ListCommand.
+ */
+ public ListCommand() {
+ this.type = "list";
+ this.description = "";
+ this.isExit = false;
+ }
+
+ /**
+ * Prints tasks in task list.
+ *
+ * @param tasks Task list.
+ * @param ui User interface.
+ * @param storage Storage.
+ * @return Output string.
+ */
+ @Override
+ public String execute(TaskList tasks, Ui ui, Storage storage) {
+ return tasks.printTaskList();
+ }
+
+ @Override
+ public boolean isExit() {
+ return this.isExit;
+ }
+}
diff --git a/src/main/java/duke/command/TaskdateCommand.java b/src/main/java/duke/command/TaskdateCommand.java
new file mode 100644
index 0000000000..28f1637382
--- /dev/null
+++ b/src/main/java/duke/command/TaskdateCommand.java
@@ -0,0 +1,60 @@
+package duke.command;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeParseException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import duke.Storage;
+import duke.ui.Ui;
+import duke.exception.DukeException;
+import duke.task.TaskList;
+
+/**
+ * Represents a command to list all tasks that corresponds to a given date.
+ */
+public class TaskdateCommand extends Command {
+
+ /**
+ * Creates a new instance of TaskdateCommand.
+ *
+ * @param description Date to be matched to current tasks.
+ */
+ public TaskdateCommand(String description) {
+ this.type = "taskdate";
+ this.description = description;
+ this.isExit = false;
+ }
+
+ /**
+ * Filters out tasks that match given date.
+ *
+ * @param tasks Task list.
+ * @param ui User interface.
+ * @param storage Storage.
+ * @return Output string.
+ * @throws DukeException If description is in the wrong format or is invalid.
+ */
+ @Override
+ public String execute(TaskList tasks, Ui ui, Storage storage) throws DukeException {
+ Pattern pattern = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}$");
+ Matcher matcher = pattern.matcher(description);
+
+ if (!matcher.find()) {
+ throw new DukeException("Your task date is given in the wrong format! "
+ + "Please make sure it is in the following format: YYYY-MM-DD");
+ }
+
+ try {
+ LocalDate date = LocalDate.parse(this.description);
+ return tasks.printTasksOn(date);
+ } catch (DateTimeParseException e) {
+ return ui.showError("This date is invalid!");
+ }
+ }
+
+ @Override
+ public boolean isExit() {
+ return this.isExit;
+ }
+}
diff --git a/src/main/java/duke/exception/DukeException.java b/src/main/java/duke/exception/DukeException.java
new file mode 100644
index 0000000000..cd8af53e12
--- /dev/null
+++ b/src/main/java/duke/exception/DukeException.java
@@ -0,0 +1,17 @@
+package duke.exception;
+
+/**
+ * Represents an exception for Duke.
+ */
+public class DukeException extends Exception {
+
+ /**
+ * Creates new instance of DukeException.
+ *
+ * @param msg Message describing the specific DukeException.
+ */
+ public DukeException(String msg) {
+ super(msg);
+ }
+
+}
diff --git a/src/main/java/duke/task/DeadlineTask.java b/src/main/java/duke/task/DeadlineTask.java
new file mode 100644
index 0000000000..77d1f92e47
--- /dev/null
+++ b/src/main/java/duke/task/DeadlineTask.java
@@ -0,0 +1,107 @@
+package duke.task;
+
+import duke.exception.DukeException;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Represents a deadline task.
+ */
+public class DeadlineTask extends Task {
+
+ protected String deadline;
+
+ /**
+ * Creates a new instance of DeadlineTask.
+ *
+ * @param description Description of deadline task.
+ * @param deadline Deadline of deadline task.
+ */
+ public DeadlineTask(String description, String deadline) {
+ super(description);
+ this.deadline = deadline;
+ }
+
+ /**
+ * Returns date of deadline.
+ *
+ * @return Date of deadline.
+ */
+ public LocalDate getDeadlineDate() throws DateTimeParseException {
+ String[] deadlineArr = this.deadline.split(" ");
+ String deadlineDateString = deadlineArr[0];
+ LocalDate deadlineDate = LocalDate.parse(deadlineDateString);
+ return deadlineDate;
+ }
+
+ /**
+ * Returns time of deadline.
+ *
+ * @return Time of deadline.
+ */
+ public LocalTime getDeadlineTime() throws DateTimeParseException {
+ String[] deadlineArr = this.deadline.split(" ");
+ String deadlineTimeString = deadlineArr[1];
+ LocalTime deadlineTime = LocalTime.parse(deadlineTimeString);
+ return deadlineTime;
+ }
+
+ /**
+ * Checks if user input has the /by keyword.
+ *
+ * @param description String input given by user after deadline.
+ * @return True if user input has the /by keyword.
+ */
+ public static boolean hasByKeyword(String description) {
+ String[] descriptionArr = description.split(" /by ");
+ return descriptionArr.length != 1;
+ }
+
+ /**
+ * Checks if user input has the correct date & time format.
+ *
+ * @param description String input given by user after deadline.
+ * @return True if user input has the correct date & time format.
+ */
+ public static boolean hasCorrectDateTimeFormat(String description) {
+ String[] descriptionArr = description.split(" /by ");
+
+ Pattern correctDateTimePattern = Pattern.compile("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}$");
+ String inputDateTimeString = descriptionArr[1];
+ Matcher matcher = correctDateTimePattern.matcher(inputDateTimeString);
+
+ return matcher.find();
+ }
+
+ /**
+ * Checks if user input has the correct format.
+ *
+ * @param description String input given by user after deadline.
+ * @throws DukeException If user input does not have the correct format.
+ */
+ public static void checkFormat(String description) throws DukeException {
+ if (!DeadlineTask.hasByKeyword(description)) {
+ throw new DukeException("Your description is not given in the correct format!");
+ } else if (!DeadlineTask.hasCorrectDateTimeFormat(description)) {
+ throw new DukeException("Your deadline is given in the wrong format! "
+ + "Please make sure it is in the following format: YYYY-MM-DD HH:MM");
+ }
+ }
+
+
+ /**
+ * Returns String representation of deadline task.
+ * @return String representation of deadline task.
+ */
+ @Override
+ public String toString() {
+ return "[D]" + "[" + getStatusIcon() + "] " + this.description
+ + " (by: " + this.getDeadlineDate().format(DateTimeFormatter.ofPattern("MMM d yyyy")) + ", "
+ + this.getDeadlineTime().format(DateTimeFormatter.ofPattern("h:mma")) + ")";
+ }
+}
diff --git a/src/main/java/duke/task/EventTask.java b/src/main/java/duke/task/EventTask.java
new file mode 100644
index 0000000000..0562cb8ae4
--- /dev/null
+++ b/src/main/java/duke/task/EventTask.java
@@ -0,0 +1,103 @@
+package duke.task;
+
+import duke.exception.DukeException;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Represents a event task.
+ */
+public class EventTask extends Task {
+
+ protected String eventDate;
+
+ /**
+ * Creates a new instance of EventTask.
+ * @param description Description of event task.
+ */
+ public EventTask(String description, String eventDate) {
+ super(description);
+ this.eventDate = eventDate;
+ }
+
+ /**
+ * Returns date of event.
+ *
+ * @return Date of event.
+ */
+ public LocalDate getEventDateDate() {
+ String[] eventDateArr = this.eventDate.split(" ");
+ String eventDateDateString = eventDateArr[0];
+ LocalDate eventDateDate = LocalDate.parse(eventDateDateString);
+ return eventDateDate;
+ }
+
+ /**
+ * Returns time of event.
+ *
+ * @return Time of event.
+ */
+ public LocalTime getEventDateTime() {
+ String[] eventDateArr = this.eventDate.split(" ");
+ String eventDateTimeString = eventDateArr[1];
+ LocalTime eventDateTime = LocalTime.parse(eventDateTimeString);
+ return eventDateTime;
+ }
+
+ /**
+ * Checks if user input has the /at keyword.
+ *
+ * @param description String input given by user after event.
+ * @return True if user input has the /at keyword.
+ */
+ public static boolean hasAtKeyword(String description) {
+ String[] descriptionArr = description.split(" /at ");
+ return descriptionArr.length != 1;
+ }
+
+ /**
+ * Checks if user input has the correct date & time format.
+ *
+ * @param description String input given by user after event.
+ * @return True if user input has the correct date & time format.
+ */
+ public static boolean hasCorrectDateTimeFormat(String description) {
+ String[] descriptionArr = description.split(" /at ");
+
+ Pattern correctDateTimePattern = Pattern.compile("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}$");
+ String inputDateTimeString = descriptionArr[1];
+ Matcher matcher = correctDateTimePattern.matcher(inputDateTimeString);
+
+ return matcher.find();
+ }
+
+ /**
+ * Checks if user input has the correct format.
+ *
+ * @param description String input given by user after event.
+ * @throws DukeException If user input does not have the correct format.
+ */
+ public static void checkFormat(String description) throws DukeException {
+ if (!EventTask.hasAtKeyword(description)) {
+ throw new DukeException("Your description is not given in the correct format!");
+ } else if (!EventTask.hasCorrectDateTimeFormat(description)) {
+ throw new DukeException("Your event date and time are given in the wrong format! "
+ + "Please make sure it is in the following format: YYYY-MM-DD HH:MM");
+ }
+ }
+
+ /**
+ * Returns String representation of event task.
+ * @return String representation of event task.
+ */
+ @Override
+ public String toString() {
+ return "[E]" + "[" + getStatusIcon() + "] " + this.description
+ + " (at: " + this.getEventDateDate().format(DateTimeFormatter.ofPattern("MMM d yyyy")) + ", "
+ + this.getEventDateTime().format(DateTimeFormatter.ofPattern("h:mma")) + ")";
+ }
+}
diff --git a/src/main/java/duke/task/Task.java b/src/main/java/duke/task/Task.java
new file mode 100644
index 0000000000..e36027670e
--- /dev/null
+++ b/src/main/java/duke/task/Task.java
@@ -0,0 +1,48 @@
+package duke.task;
+
+/**
+ * Represents a task in a task list.
+ */
+public class Task {
+ protected String description;
+ protected boolean isDone;
+
+ /**
+ * Creates a new instance of Task.
+ * The task is marked as not done by default.
+ *
+ * @param description Description of task.
+ */
+ public Task(String description) {
+ this.description = description;
+ this.isDone = false;
+ }
+
+ /**
+ * Returns the status icon of the task, based on whether it is done or not.
+ *
+ * @return X for done or SPACE for not done.
+ */
+ public String getStatusIcon() {
+ return (isDone) ? "X" : " "; //return tick or X symbols
+ }
+
+ /**
+ * Marks a task as done.
+ *
+ * @return Output string.
+ */
+ public String markAsDone() {
+ this.isDone = true;
+ return "Nice! I've marked this task as done: \n " + this;
+ }
+
+ /**
+ * Returns a String representation of a task.
+ *
+ * @return String representation of a task.
+ */
+ public String toString() {
+ return "[" + getStatusIcon() + "] " + this.description;
+ }
+}
diff --git a/src/main/java/duke/task/TaskList.java b/src/main/java/duke/task/TaskList.java
new file mode 100644
index 0000000000..05662a1fec
--- /dev/null
+++ b/src/main/java/duke/task/TaskList.java
@@ -0,0 +1,226 @@
+package duke.task;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+
+import duke.FileManager;
+import duke.Storage;
+import duke.exception.DukeException;
+
+/**
+ * Represents a list of tasks.
+ */
+public class TaskList {
+ protected List tasks;
+
+ /**
+ * Creates a new instance of TaskList when no existing tasks are available.
+ */
+ public TaskList() {
+ this.tasks = new ArrayList<>();
+ }
+
+ /**
+ * Creates a new instance of TaskList when existing tasks are available.
+ *
+ * @param existingTaskList TaskList with existing tasks.
+ */
+ public TaskList(TaskList existingTaskList) {
+ this.tasks = existingTaskList.tasks;
+ }
+
+ /**
+ * Returns this task list.
+ *
+ * @param taskNo Task number.
+ * @return Task for the given task number.
+ */
+ public Task getTask(int taskNo) {
+ return this.tasks.get(taskNo - 1);
+ }
+
+ public int getTaskListSize() {
+ return this.tasks.size();
+ }
+
+ public boolean hasTaskNumber(int taskNo) {
+ return (taskNo > 0) && (taskNo <= this.tasks.size());
+ }
+
+ /**
+ * Adds task to task list.
+ *
+ * @param taskType Type of task.
+ * @param description Description of task.
+ * @param isDone Task is done or not.
+ * @param isReadingFile True if a file is being read, false if a file is not being read.
+ * @param storage Storage.
+ *
+ * @return Output string.
+ * @throws DukeException If description is not given in the correct format.
+ * @throws IOException If there are any input and output issues.
+ */
+ public String addTask(TaskType taskType, String description, boolean isDone, boolean isReadingFile, Storage storage)
+ throws DukeException, IOException {
+ Task newTask = new Task(description);
+ if (taskType == TaskType.TODO) {
+ newTask = new ToDoTask(description);
+ } else if (taskType == TaskType.DEADLINE) {
+ DeadlineTask.checkFormat(description);
+ String[] descriptionArr = description.split(" /by ");
+ String info = descriptionArr[0];
+ String dateTime = descriptionArr[1];
+ newTask = new DeadlineTask(info, dateTime);
+ } else if (taskType == TaskType.EVENT) {
+ EventTask.checkFormat(description);
+ String[] descriptionArr = description.split(" /at ");
+ String info = descriptionArr[0];
+ String dateTime = descriptionArr[1];
+ newTask = new EventTask(info, dateTime);
+ }
+
+ if (isReadingFile) {
+ if (isDone) {
+ newTask.markAsDone();
+ }
+ this.tasks.add(newTask);
+ return "";
+ } else {
+ storage.checkForDuplicate(newTask.toString());
+ this.tasks.add(newTask);
+ storage.appendToFile("data/duke.txt", newTask.toString());
+ return ("Got it. I've added this task: \n"
+ + " " + newTask + "\n"
+ + "Now you have " + this.tasks.size() + " tasks in the list.");
+ }
+ }
+
+ /**
+ * Deletes task from task list.
+ *
+ * @param taskNo Task number.
+ * @param storage Storage.
+ * @return Output string.
+ * @throws DukeException If task number does not exist.
+ */
+ public String deleteTask(int taskNo, Storage storage) throws DukeException, IOException {
+ if (!this.hasTaskNumber(taskNo)) {
+ throw new DukeException("☹ OOPS!!! This task number does not exist.");
+ }
+
+ String message;
+ int taskIndex = taskNo - 1;
+
+ message = "Noted. I've removed this task:\n"
+ + " " + this.tasks.get(taskIndex);
+ this.tasks.remove(taskIndex);
+ String taskOrTasks = (this.tasks.size() <= 1)
+ ? " task"
+ : " tasks";
+ message += "\nNow you have " + this.tasks.size() + taskOrTasks + " in the list.";
+ FileManager.deleteLine("data/duke.txt", taskNo, storage);
+
+ return message;
+
+ }
+
+
+
+ /**
+ * Prints tasks in task list.
+ *
+ * @return Output string.
+ */
+ public String printTaskList() {
+ if (this.tasks.size() == 0) {
+ return "There are currently no tasks in your list!";
+ } else {
+ String listOfTasks = "Here are the tasks in your list:\n";
+ for (int i = 1; i <= this.tasks.size(); i++) {
+ listOfTasks += i + "." + this.tasks.get(i - 1) + "\n";
+ }
+ return listOfTasks;
+ }
+ }
+
+ /**
+ * Prints tasks without header and number labels.
+ *
+ * @return Output string.
+ */
+ public String printTaskListWithoutNumbers() {
+ String listOfTasks = "";
+ for (Task task : this.tasks) {
+ listOfTasks += task + "\n";
+ }
+ return listOfTasks;
+ }
+
+ /**
+ * Prints tasks that match the given date.
+ *
+ * @param date Date to be matched to tasks.
+ * @return Output string.
+ */
+ public String printTasksOn(LocalDate date) {
+ List list = new ArrayList();
+ for (int i = 0; i < this.tasks.size(); i++) {
+ Task task = tasks.get(i);
+ if (task instanceof DeadlineTask) {
+ LocalDate existingDate = ((DeadlineTask) task).getDeadlineDate();
+ if (existingDate.compareTo(date) == 0) {
+ list.add(task);
+ }
+ } else if (task instanceof EventTask) {
+ LocalDate existingDate = ((EventTask) task).getEventDateDate();
+ if (existingDate.compareTo(date) == 0) {
+ list.add(task);
+ }
+ } else {
+ continue;
+ }
+
+ }
+
+ if (list.size() == 0) {
+ return "There are no tasks with this date!";
+ } else {
+ String listOfTasks = ("Here are the tasks on "
+ + date.format(DateTimeFormatter.ofPattern("MMM d yyyy"))
+ + " in your list:\n");
+ for (Task task : list) {
+ listOfTasks += task + "\n";
+ }
+ return listOfTasks;
+ }
+ }
+
+ /**
+ * Prints tasks that match the given keyword.
+ *
+ * @param keyword Keyword to be matched to tasks.
+ * @return Output string.
+ */
+ public String printTasksWith(String keyword) {
+ List list = new ArrayList();
+ for (Task task : this.tasks) {
+ if (task.description.contains(keyword)) {
+ list.add(task);
+ }
+ }
+
+ if (list.size() == 0) {
+ return "There are no matching tasks with this keyword!";
+ } else {
+ String listOfTasks = "Here are the matching tasks in your list:\n";
+ for (int i = 1; i <= list.size(); i++) {
+ listOfTasks += i + "." + list.get(i - 1) + "\n";
+ }
+ return listOfTasks;
+ }
+ }
+
+}
diff --git a/src/main/java/duke/task/TaskType.java b/src/main/java/duke/task/TaskType.java
new file mode 100644
index 0000000000..a3d94a97de
--- /dev/null
+++ b/src/main/java/duke/task/TaskType.java
@@ -0,0 +1,8 @@
+package duke.task;
+
+/**
+ * Represents the different task types available.
+ */
+public enum TaskType {
+ TODO, DEADLINE, EVENT;
+}
diff --git a/src/main/java/duke/task/ToDoTask.java b/src/main/java/duke/task/ToDoTask.java
new file mode 100644
index 0000000000..4c7b9372fb
--- /dev/null
+++ b/src/main/java/duke/task/ToDoTask.java
@@ -0,0 +1,24 @@
+package duke.task;
+
+/**
+ * Represents a todo task.
+ */
+public class ToDoTask extends Task {
+
+ /**
+ * Creates a new instance of ToDoTask.
+ * @param description Description of todo task.
+ */
+ public ToDoTask(String description) {
+ super(description);
+ }
+
+ /**
+ * Returns String representation of todo task.
+ * @return String representation of todo task.
+ */
+ @Override
+ public String toString() {
+ return "[T]" + "[" + getStatusIcon() + "] " + this.description;
+ }
+}
diff --git a/src/main/java/duke/ui/DialogBox.java b/src/main/java/duke/ui/DialogBox.java
new file mode 100644
index 0000000000..f27ca93eba
--- /dev/null
+++ b/src/main/java/duke/ui/DialogBox.java
@@ -0,0 +1,61 @@
+package duke.ui;
+
+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/ui/MainWindow.java b/src/main/java/duke/ui/MainWindow.java
new file mode 100644
index 0000000000..f4bc462e76
--- /dev/null
+++ b/src/main/java/duke/ui/MainWindow.java
@@ -0,0 +1,62 @@
+package duke.ui;
+
+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;
+
+import duke.Duke;
+
+/**
+ * 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/DaUser.png"));
+ private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png"));
+
+ @FXML
+ public void initialize() {
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+ showWelcomeMessage();
+ }
+
+ 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() {
+ String input = userInput.getText();
+ String response = duke.getResponse(input);
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialog(input, userImage),
+ DialogBox.getDukeDialog(response, dukeImage)
+ );
+ userInput.clear();
+ }
+
+ @FXML
+ private void showWelcomeMessage() {
+ dialogContainer.getChildren().addAll(
+ DialogBox.getDukeDialog(Ui.showWelcome(), dukeImage)
+ );
+ }
+}
diff --git a/src/main/java/duke/ui/Ui.java b/src/main/java/duke/ui/Ui.java
new file mode 100644
index 0000000000..c67f347b9a
--- /dev/null
+++ b/src/main/java/duke/ui/Ui.java
@@ -0,0 +1,56 @@
+package duke.ui;
+
+import java.util.Scanner;
+
+/**
+ * Represents a user interface that interacts with the user.
+ */
+public class Ui {
+
+ /**
+ * Shows the introductory message.
+ */
+ public static String showWelcome() {
+
+ String logo = " ____ _ \n"
+ + "| _ \\ _ _| | _____ \n"
+ + "| | | | | | | |/ / _ \\\n"
+ + "| |_| | |_| | < __/\n"
+ + "|____/ \\__,_|_|\\_\\___|\n";
+ return "Hello from Duke!";
+ }
+
+ /**
+ * Shows the exit information.
+ */
+ public String showExit() {
+ return "Bye. Hope to see you again soon!";
+ }
+
+ /**
+ * Shows a line break.
+ */
+ public void showLine() {
+ System.out.println("____________________________________________________________");
+ }
+
+ /**
+ * Shows error messages.
+ *
+ * @param message Error message.
+ */
+ public String showError(String message) {
+ return message;
+ }
+
+ /**
+ * Reads user's command.
+ *
+ * @return User's full command.
+ */
+ public String readCommand() {
+ Scanner sc = new Scanner(System.in);
+ return sc.nextLine();
+ }
+
+}
diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png
new file mode 100644
index 0000000000..f99f20222f
Binary files /dev/null and b/src/main/resources/images/DaDuke.png differ
diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png
new file mode 100644
index 0000000000..ece17e9471
Binary files /dev/null and b/src/main/resources/images/DaUser.png differ
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 0000000000..5e176c3b34
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
new file mode 100644
index 0000000000..d28683a8a2
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/duke/ParserTest.java b/src/test/duke/ParserTest.java
new file mode 100644
index 0000000000..a350153e23
--- /dev/null
+++ b/src/test/duke/ParserTest.java
@@ -0,0 +1,25 @@
+package duke;
+
+import duke.command.AddCommand;
+import duke.command.Command;
+import duke.exception.DukeException;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class ParserTest {
+
+ @Test
+ void parse_validCommand_success() {
+ assertDoesNotThrow(() -> {
+ Command command = Parser.parse("todo homework");
+ });
+ }
+
+ @Test
+ void parse_invalidCommand_exceptionThrown() {
+ assertThrows(DukeException.class, () -> {
+ Command command = Parser.parse("blah");
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/test/duke/task/DeadlineTaskTest.java b/src/test/duke/task/DeadlineTaskTest.java
new file mode 100644
index 0000000000..fc98efd71e
--- /dev/null
+++ b/src/test/duke/task/DeadlineTaskTest.java
@@ -0,0 +1,48 @@
+package duke.task;
+
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.format.DateTimeParseException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class DeadlineTaskTest {
+
+ @Test
+ void getDeadlineDate_validDate_success() {
+ DeadlineTask deadlineTask = new DeadlineTask("homework", "2021-01-01 19:00");
+ LocalDate validDate = LocalDate.parse("2021-01-01");
+ assertEquals(deadlineTask.getDeadlineDate(), validDate);
+ }
+
+ @Test
+ void getDeadlineDate_invalidDate_exceptionThrown() {
+ assertThrows(DateTimeParseException.class, () -> {
+ DeadlineTask deadlineTask = new DeadlineTask("homework", "01-01-2021 19:00");
+ deadlineTask.getDeadlineDate();
+ });
+ }
+
+ @Test
+ void getDeadlineTime_validTime_success() {
+ DeadlineTask deadlineTask = new DeadlineTask("homework", "2021-01-01 19:00");
+ LocalTime validTime = LocalTime.parse("19:00");
+ assertEquals(deadlineTask.getDeadlineTime(), validTime);
+ }
+
+ @Test
+ void getDeadlineTime_invalidTime_exceptionThrown() {
+ assertThrows(DateTimeParseException.class, () -> {
+ DeadlineTask deadlineTask = new DeadlineTask("homework", "01-01-2021 25:00");
+ deadlineTask.getDeadlineTime();
+ });
+ }
+
+ @Test
+ void toString_validInputs_success() {
+ assertEquals(new DeadlineTask("homework", "2021-01-01 19:00").toString(),
+ "[D][ ] homework (by: Jan 1 2021, 7:00PM)");
+ }
+}
\ No newline at end of file
diff --git a/src/test/duke/task/TaskTest.java b/src/test/duke/task/TaskTest.java
new file mode 100644
index 0000000000..8b1b5a9adc
--- /dev/null
+++ b/src/test/duke/task/TaskTest.java
@@ -0,0 +1,22 @@
+package duke.task;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TaskTest {
+
+ @Test
+ void markAsDone_validInput_success() {
+ Task task = new DeadlineTask("homework", "2021-01-01 19:00");
+ task.markAsDone();
+ assertEquals(task.isDone, true);
+ }
+
+ @Test
+ void getStatusIcon_validInput_success() {
+ Task task = new DeadlineTask("homework", "2021-01-01 19:00");
+ assertEquals(task.getStatusIcon(), " ");
+ }
+
+}
\ No newline at end of file
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 657e74f6e7..c309a6692c 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -5,3 +5,18 @@ Hello from
| |_| | |_| | < __/
|____/ \__,_|_|\_\___|
+Got it. I've added this task:
+ [T][ ] borrow book
+Now you have 5 tasks in the list.
+Here are the tasks in your list:
+1.[T][X] read book
+2.[D][ ] return book (by: June 6th)
+3.[E][ ] project meeting (at: Aug 6th 2-4pm)
+4.[T][X] join sports club
+5.[T][ ] borrow book
+Got it. I've added this task:
+ [D][ ] return book (by: Sunday)
+Now you have 6 tasks in the list.
+Got it. I've added this task:
+ [E][ ] project meeting (at: Mon 2-4pm)
+Now you have 7 tasks in the list.
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index e69de29bb2..5b9e97762f 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -0,0 +1,4 @@
+todo borrow book
+list
+deadline return book /by Sunday
+event project meeting /at Mon 2-4pm
\ No newline at end of file
diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh
old mode 100644
new mode 100755