diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..a23efb5028
--- /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 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.gui.Launcher"
+}
+
+shadowJar {
+ archiveBaseName = "duke"
+ archiveClassifier = null
+}
+
+checkstyle {
+ 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..b1e659a3f8
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,403 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
new file mode 100644
index 0000000000..dcaa1af3c3
--- /dev/null
+++ b/config/checkstyle/suppressions.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
index 8077118ebe..a5436d7d07 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -2,28 +2,225 @@
## Features
-### Feature-ABC
+### Add Tasks
+There are a few types of tasks that can be added:
+1. A basic ```Todo``` task containing a description
+2. A ```Deadline``` task that need to be done before a specific date/time
+3. An ```Event``` task that occurs at a specific date/time/location
+4. A ```DoAfter``` task that needs to be done after a specific time/task
-Description of the feature.
+
+Note: Automatic Date & Time Formatting
-### Feature-XYZ
+Dates in the form of YYYY/MM/DD, or with delimiters - | / . will be converted to MMM d yyyy format. Time in HHMM format will similarly be converted to HH:MM format.
-Description of the feature.
+
+### List tasks
+Tasks that have been added so far can be listed.
+
+### Delete task
+Task at a specified index can be deleted.
+
+### Search for tasks
+Matching tasks containing a keyword can be found and listed.
+
+### Marking tasks as (in)complete
+
+Tasks at a specified index can be marked as complete or incomplete.
## Usage
-### `Keyword` - Describe action
+### `todo` - Add a Todo task
+
+Adds a ```Todo``` task containing a description.
+
+Example of usage:
+
+`todo collect primogems`
+
+Expected outcome:
+
+Task of the type ```Todo``` containing the description 'read a book' will be added.
+
+```
+Got it! I have noted down the following task in your list.
+[T][ ] collect primogems
+```
+
+
+
+### `deadline` - Add a Deadline task
+
+Adds a ```Deadline``` task containing a description and a datetime string.
+
+Example of usage:
+
+`deadline wish on Zhongli's banner by 2022/02/18 2359 GMT +8`
+
+Expected outcome:
+
+Task of the type ```Deadline``` containing the description 'wish on Zhongli's banner' and formatted datetime string 'Feb 18 2022 23:59 GMT +8' will be added.
+
+```
+Got it! I have noted down the following task in your list.
+Remember the deadline!
+[D][ ] wish on Zhongli's banner (by: Feb 18 2022 23:59 GMT +8)
+```
+
+
+
+### `event` - Add an event task
-Describe the action and its outcome.
+Adds an ```Event``` task containing a description and a datetime string.
Example of usage:
-`keyword (optional arguments)`
+`event Windtrace festival at 2022/02/08 online`
+
+Expected outcome:
+
+Task of the type ```Event``` containing the description 'Windtrace festival' and formatted datetime string 'Feb 8 2022 online' will be added.
+
+```
+Got it! I have noted down the following task in your list.
+Do be there on time!
+[E][ ] Windtrace festival (at: Feb 8 2022 online)
+```
+
+
+
+### `doafter` - Add a DoAfter task
+
+Adds a ```DoAfter``` task containing a description and a datetime string.
+
+Example of usage:
+
+`doafter collect 900 primogems after reaching AR 15`
+
+Expected outcome:
+
+Task of the type ```DoAfter``` containing the description 'collect 900 primogems' and formatted datetime string 'reaching AR 15' will be added.
+
+```
+Got it! I have noted down the following task in your list.
+Don't forget it!
+[A][ ] collect 900 primogems (at: reaching AR 15)
+```
+
+
+
+### `list` - List all tasks
+
+Lists all tasks thus far.
+
+Example of usage:
+
+`list`
+
+Expected outcome:
+A printed list of all saved tasks and the total number of tasks.
+
+```
+Hmm... Paimon keeps a clear record in her diary.
+ 1. [T][ ] collect primogems
+ 2. [D][ ] wish on Zhongli's banner (by: Feb 18 2022 23:59 GMT +8)
+ 3. [E][ ] Windtrace festival (at: Feb 8 2022 online)
+ 4. [A][ ] collect 900 primogems (at: reaching AR 15)
+You have 4 tasks on your list.
+```
+
+
+
+### `delete` - Delete a task
+
+Deletes a task at a specified index.
+
+Example of usage:
+
+```delete 2```
+
+Expected outcome:
+
+Task at the specified index will be removed. The resulting list will be printed.
+
+```
+Noted, the task has been scrubbed off the list!
+ 1. [T][ ] collect primogems
+ 2. [E][ ] Windtrace festival (at: Feb 8 2022 online)
+ 3. [A][ ] collect 900 primogems (at: reaching AR 15)
+You have 3 tasks on your list.
+```
+
+
+
+### `do` - Do a task
+
+Marks a task at a specified index as complete.
+
+Example of usage:
+
+```do 1```
+
+Expected outcome:
+
+Task at the specified index will be marked complete.
+
+```
+Task successfully updated.
+```
+A subsequent ```list``` command will result in
+```
+Noted, the task has been scrubbed off the list!
+ 1. [T][✓] collect primogems
+ 2. [E][ ] Windtrace festival (at: Feb 8 2022 online)
+ 3. [A][ ] collect 900 primogems (at: reaching AR 15)
+You have 3 tasks on your list.
+```
+
+
+
+### `undo` - Undo a task
+
+Marks a task at a specified index as incomplete.
+
+Example of usage:
+
+```undo 1```
+
+Expected outcome:
+
+Task at the specified index will be marked complete.
+
+```
+Task successfully updated.
+```
+A subsequent ```list``` command will result in
+```
+Noted, the task has been scrubbed off the list!
+ 1. [T][ ] collect primogems
+ 2. [E][ ] Windtrace festival (at: Feb 8 2022 online)
+ 3. [A][ ] collect 900 primogems (at: reaching AR 15)
+You have 3 tasks on your list.
+```
+
+
+
+
+### `find` - Search for keyword
+
+Finds and lists tasks containing a keyword. The keyword is case-sensitive.
+
+Example of usage:
+
+```find collect```
Expected outcome:
-Description of the outcome.
+The list of tasks containing the keyword 'collect', and the number of such tasks.
```
-expected output
+Here are the matching tasks in your list:
+ 1. [T][ ] collect primogems
+ 2. [A][ ] collect 900 primogems (at: reaching AR 15)
+You have 2 tasks on your list.
```
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..10d92a87e9
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..b71055e9b8
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1,5 @@
+markdown: kramdown
+highlighter: rouge
+kramdown:
+ input: GFM
+ hard_wrap: false
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..2c9a9745c5
--- /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..b9e2753c97
--- /dev/null
+++ b/src/main/java/duke/Duke.java
@@ -0,0 +1,253 @@
+package duke;
+
+import duke.task.Deadline;
+import duke.task.DoAfter;
+import duke.task.Event;
+import duke.task.TaskList;
+import duke.task.Todo;
+
+/**
+ * The Duke program implements a simple task bot with CRUD functionality.
+ * The program can add three different types of tasks (todo, deadline,
+ * event), mark tasks as done, and delete tasks.
+ *
+ * @author Elumalai Oviya Dharshini
+ * @version 1.1
+ */
+public class Duke {
+ private Storage storage;
+ private TaskList tasks;
+
+ /**
+ * Constructor for Duke specifying UI and Storage.
+ *
+ * It loads Tasks from a file into tasks.
+ * If there is an error with loading Tasks from the specified file, it
+ * initializes tasks to bean empty TaskList.
+ */
+ public Duke(String filePath) {
+ try {
+ storage = new Storage(filePath);
+ tasks = new TaskList(storage.load());
+ } catch (Exception e) {
+ System.out.println(Ui.showLoadingError());
+ tasks = new TaskList();
+ }
+ }
+
+ /**
+ * Marks Task as complete.
+ *
+ * @param input index of task
+ * @return Duke's response and expression type.
+ * @throws Exception if an exception occurs in the saving of
+ * data to the file at filePath or NumberFormatException
+ */
+ public Response doHandler(String input) throws Exception {
+
+ int i = Integer.parseInt(input.replaceAll("[^0-9]",
+ "")) - 1;
+ assert i <= tasks.size() && i >= 0 : "index of item should be within scope of tasklist";
+ tasks.get(i).markComplete();
+ assert tasks.get(i).getStatusIcon().equals("\u2713") : "task should be marked done";
+
+ storage.save(tasks);
+ return new Response(Ui.showDoMessage(), Expression.HAPPY);
+ }
+
+ /**
+ * Marks Task as incomplete.
+ *
+ * @param input index of task
+ * @return Duke's response and expression type.
+ * @throws Exception if an exception occurs in the saving of
+ * data to the file at filePath or NumberFormatException
+ */
+ public Response undoHandler(String input) throws Exception {
+ int i = Integer.parseInt(input.replaceAll("[^0-9]",
+ "")) - 1;
+ assert i <= tasks.size() && i >= 0 : "index of item should be within scope of tasklist";
+ tasks.get(i).markIncomplete();
+ assert tasks.get(i).getStatusIcon().equals(" ") : "task should be marked incomplete";
+
+ storage.save(tasks);
+ return new Response(Ui.showUndoMessage(), Expression.DISAPPOINTED);
+ }
+
+ /**
+ * Deletes Task at a specified index from tasks.
+ *
+ * @param input index of task
+ * @return Response of list of tasks, deletion message, and expression type.
+ * @throws Exception if an exception occurs in the saving of
+ * data to the file at filePath or NumberFormatException
+ */
+ public Response deleteHandler(String input) throws Exception {
+ int i = Integer.parseInt(input.replaceAll("[^0-9]",
+ "")) - 1;
+ assert i <= tasks.size() && i >= 0 : "index of item should be within scope of tasklist";
+ tasks.remove(i);
+
+ storage.save(tasks);
+ return new Response(Ui.showDeleteMessage(tasks), Expression.THUMBSUP);
+ }
+
+ /**
+ * Returns a string of the list of Tasks.
+ *
+ * @return Response of formatted string of list of tasks and
+ * expression type.
+ */
+ public Response listHandler() {
+ return new Response(Ui.showListMessage(tasks), Expression.DEFAULT);
+ }
+
+ /**
+ * Adds a Todo to tasks and returns string about the addition.
+ *
+ * @param input description of Todo
+ * @return Duke's response and expression type.
+ * @throws Exception if an exception occurs in the saving of
+ * data to the file at filePath
+ */
+ public Response todoHandler(String input) throws Exception {
+ Todo t = new Todo(input);
+ tasks.add(t);
+
+ storage.save(tasks);
+ return new Response(Ui.showTodoMessage() + t, Expression.THUMBSUP);
+ }
+
+
+ /**
+ * Finds tasks with matching keywords in tasklist and returns them.
+ *
+ * @param input keyword to search for
+ * @return Response of matching tasks and expression type.
+ */
+ public Response findHandler(String input) {
+ return new Response(Ui.showFindMessage(tasks.find(input)),
+ Expression.DEFAULT);
+ }
+
+ /**
+ * Adds a Deadline task to tasks and returns string about the addition.
+ *
+ * @param input string containing description and dateTime of task
+ * @return Duke's response and expression type.
+ * @throws Exception if an exception occurs in the saving of
+ * data to the file at filePath
+ */
+ public Response deadlineHandler(String input) throws Exception {
+ String datetime = input.replaceAll(".* by ", "");
+ input = input.replaceAll(" by .*", "");
+
+ Deadline d = new Deadline(input, datetime);
+ tasks.add(d);
+
+ storage.save(tasks);
+ return new Response(Ui.showDeadlineMessage() + d, Expression.THUMBSUP);
+ }
+
+ /**
+ * Adds an Event task to tasks and returns string about the addition.
+ *
+ * @param input string containing description and dateTime of task
+ * @return Duke's response and expression type.
+ * @throws Exception if an exception occurs in the saving of
+ * data to the file at filePath
+ */
+ public Response eventHandler(String input) throws Exception {
+ String time = input.replaceAll(".* at ", "");
+ input = input.replaceAll(" at .*", "");
+ Event e = new Event(input, time);
+ tasks.add(e);
+ storage.save(tasks);
+ return new Response(Ui.showEventMessage() + e, Expression.THUMBSUP);
+ }
+
+ /**
+ * Adds a doAfter task to tasks and returns string about the addition.
+ *
+ * @param input string containing description and dateTime of task
+ * @return Duke's response and expression type.
+ * @throws Exception if an exception occurs in the saving of
+ * data to the file at filePath
+ */
+ public Response doAfterHandler(String input) throws Exception {
+ String time = input.replaceAll(".* after ", "");
+ input = input.replaceAll(" after .*", "");
+ DoAfter e = new DoAfter(input, time);
+ tasks.add(e);
+ storage.save(tasks);
+ return new Response(Ui.showDoAfterMessage() + e, Expression.THUMBSUP);
+ }
+
+ /**
+ * Returns 'EXIT' string to signal program termination.
+ *
+ * @return Duke's response and expression type.
+ */
+ public Response byeHandler() {
+ Response r = new Response(Ui.showByeMessage(), Expression.DEFAULT);
+ r.setExit();
+ return r;
+ }
+
+ /**
+ * Returns response to indecipherable input.
+ *
+ * @param input string to respond to
+ * @return generic response indicative of incorrect input and
+ * expression type.
+ */
+ public Response defaultHandler(String input) {
+ String response = input.equals("")
+ ? Ui.showEmptyMessage()
+ : Ui.showDefaultMessage();
+ return new Response(response, Expression.DEFAULT);
+ }
+
+
+ /**
+ * Determines Duke's responses to user input.
+ *
+ * @param input user input
+ * @return Duke's response and expression type.
+ */
+ public Response getResponse(String input) {
+ try {
+ String command = Parser.parse(input, tasks);
+ input = Parser.handleInput(input);
+
+ switch (command) {
+ case "list":
+ return listHandler();
+ case "do":
+ return doHandler(input);
+ case "undo":
+ return undoHandler(input);
+ case "delete":
+ return deleteHandler(input);
+ case "todo":
+ return todoHandler(input);
+ case "find":
+ return findHandler(input);
+ case "deadline":
+ return deadlineHandler(input);
+ case "event":
+ return eventHandler(input);
+ case "doafter":
+ return doAfterHandler(input);
+ case "bye":
+ return byeHandler();
+ default:
+ return defaultHandler(input);
+ }
+ } catch (NumberFormatException e) {
+ return new Response(Ui.showNumberFormatMessage(), Expression.DISAPPOINTED);
+ } catch (Exception e) {
+ return new Response(Ui.showError(e.getMessage()), Expression.DISAPPOINTED);
+ }
+ }
+}
diff --git a/src/main/java/duke/Expression.java b/src/main/java/duke/Expression.java
new file mode 100644
index 0000000000..1d4e93d2c5
--- /dev/null
+++ b/src/main/java/duke/Expression.java
@@ -0,0 +1,8 @@
+package duke;
+
+public enum Expression {
+ DEFAULT,
+ DISAPPOINTED,
+ HAPPY,
+ THUMBSUP
+}
diff --git a/src/main/java/duke/Main.java b/src/main/java/duke/Main.java
new file mode 100644
index 0000000000..ab5a129dd0
--- /dev/null
+++ b/src/main/java/duke/Main.java
@@ -0,0 +1,42 @@
+package duke;
+
+import java.io.IOException;
+
+import duke.gui.MainWindow;
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+
+import javafx.scene.Scene;
+import javafx.scene.image.Image;
+import javafx.scene.layout.AnchorPane;
+
+import javafx.stage.Stage;
+
+/**
+ * A GUI for Duke using FXML.
+ */
+public class Main extends Application {
+ private static final String FILEPATH = "data/tasks.txt";
+ private final Duke duke = new Duke(FILEPATH);
+
+ @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.getIcons().add(new Image(this.getClass().getResourceAsStream("/images/Icon.png")));
+ stage.setTitle("The Traveller's Handbook");
+ stage.setResizable(false);
+ scene.getStylesheets().add("https://fonts.googleapis.com/css2?family=Open+Sans");
+ scene.getStylesheets().add(getClass().getResource("/style.css").toExternalForm());
+
+ fxmlLoader.getController().setDuke(duke);
+ stage.show();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/duke/Parser.java b/src/main/java/duke/Parser.java
new file mode 100644
index 0000000000..5f92fdf178
--- /dev/null
+++ b/src/main/java/duke/Parser.java
@@ -0,0 +1,130 @@
+package duke;
+
+import duke.task.TaskList;
+
+/**
+ * Represents a class that validates and interprets user input with a
+ * pre-generated list of allowable input formats.
+ * Note: Current allowable formats are "list", "bye", "do X", "undo X",
+ * "delete X", "todo S", "deadline S by T", "event S at T", "find W",
+ * where X is an integer, S is a string descriptor of the task, W is a
+ * singular keyword and T is a string descriptor of the date(s) and/or
+ * time(s) associated with the task.
+ *
+ * @author Elumalai Oviya Dharshini
+ * @version 1.1
+ */
+public class Parser {
+
+ /**
+ * Extracts the non-command contents of a given input string.
+ * Strips the string of leading whitespaces and removes the first word.
+ *
+ * @param input input string
+ * @return input consisting of the non-command contents of the input string
+ */
+ public static String handleInput(String input) {
+ input = input.trim();
+ if (input.contains(" ")) {
+ input = input.substring(input.indexOf(" "));
+ }
+ return input;
+ }
+
+ /**
+ * Validates input format and extracts command.
+ * Note: Current allowable formats are "list", "bye", "do X", "undo X",
+ * "delete X", "todo S", "find W", "deadline S by T", "event S at T",
+ * where X is an integer, S is a string descriptor of the task, W is a
+ * singular keyword and T is a string descriptor of the date(s) and/or
+ * time(s) associated with the task.
+ *
+ * @param input input string to be parsed
+ * @return command from input string if input is of a valid format,
+ * "" otherwise
+ * @throws Exception if input string is not of an expected format
+ */
+ public static String parse(String input, TaskList tasklist) throws Exception {
+ input = input.trim();
+ String command = input.replaceAll(" .*", "");
+
+ input = input.trim();
+ if (input.equals("bye") || input.equals("list")) {
+ return command;
+ }
+
+ // Handle do, undo, delete
+ String firstWord = input.replaceAll(" .*", "");
+ input = input.substring(firstWord.length()).trim();
+
+ switch (firstWord) {
+ case "do":
+ // Fallthrough
+ case "undo":
+ // Fallthrough
+ case "delete":
+ int index = Integer.parseInt(input);
+ input = input.replaceAll(".* ", "");
+ if (input.matches("[0-9]+") && index <= tasklist.size()
+ && index >= 1) {
+ assert index <= tasklist.size() : "index not within range";
+ return command;
+ }
+
+ throw new Exception("You need to specify the task you want to "
+ + firstWord + " by its index :c");
+ case "todo":
+ if (input.equals("")) {
+ throw new Exception("Oops, you need to mention what the "
+ + "task is :c");
+ }
+ return command;
+ case "deadline":
+ if (!input.contains(" by ")) {
+ throw new Exception("Oops, you need to format deadline tasks "
+ + "as \"deadline X by Y\" :c");
+ }
+
+ String lastWord = input.substring(input.lastIndexOf(" ") + 1);
+ if (lastWord.equals("by")) {
+ return "";
+ }
+ return command;
+ case "event":
+ if (!input.contains(" at ")) {
+ throw new Exception("Oops, you need to format event tasks "
+ + "as \"event X at Y\" :c");
+ }
+
+ String finalWord = input.substring(input.lastIndexOf(" ") + 1);
+ if (finalWord.equals("at")) {
+ return "";
+ }
+ return command;
+ case "doafter":
+ if (!input.contains(" after ")) {
+ throw new Exception("Oops, you need to format doafter tasks "
+ + "as \"doafter X after Y\" :c");
+ }
+
+ String endWord = input.substring(input.lastIndexOf(" ") + 1);
+ if (endWord.equals("after")) {
+ return "";
+ }
+ return command;
+ case "find":
+ if (input.equals("")) {
+ throw new Exception("Oops, you need to mention what "
+ + "the keyword is :c");
+ }
+ if (input.contains(" ")) {
+ throw new Exception("Oops, you can only search for "
+ + "one keyword at a time :c");
+ }
+
+ return command;
+ default:
+ return "";
+ }
+ }
+}
diff --git a/src/main/java/duke/Response.java b/src/main/java/duke/Response.java
new file mode 100644
index 0000000000..0385cbbbe8
--- /dev/null
+++ b/src/main/java/duke/Response.java
@@ -0,0 +1,40 @@
+package duke;
+
+/**
+ * Represents a Response class that contains Duke's response to user input.
+ *
+ * @author Elumalai Oviya Dharshini
+ * @version 1.1
+ */
+public class Response {
+ private Expression expression;
+ private String message;
+ private Boolean isExit = false;
+
+ /**
+ * Constructor for Response.
+ *
+ * @param message Duke's text response
+ * @param expression Duke's visual expression
+ */
+ public Response(String message, Expression expression) {
+ this.expression = expression;
+ this.message = message;
+ }
+
+ public Expression getExpression() {
+ return expression;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setExit() {
+ isExit = true;
+ }
+
+ public boolean isExit() {
+ return isExit;
+ }
+}
diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/Storage.java
new file mode 100644
index 0000000000..9d424c62cb
--- /dev/null
+++ b/src/main/java/duke/Storage.java
@@ -0,0 +1,194 @@
+package duke;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Scanner;
+
+import duke.task.Deadline;
+import duke.task.DoAfter;
+import duke.task.Event;
+import duke.task.Task;
+import duke.task.TaskList;
+import duke.task.Todo;
+
+/**
+ * Represents a storage space for tasks on hard-drive.
+ * It handles the loading of tasks from a file and saving
+ * of tasks to the same file.
+ *
+ * @author Elumalai Oviya Dharshini
+ * @version 1.1
+ */
+public class Storage {
+ private String filePath;
+ private ArrayList tasks;
+
+ /**
+ * Constructor for Storage.
+ * Initializes the Storage object with a given file path.
+ * It parses input from the specified file and saves the list of Tasks from
+ * the specified file, if any. If file does not exist, it creates the file.
+ * Note: any missing parent directories in the specified file path are
+ * created prior to file creation.
+ *
+ * @param filePath path of the specified file from the current directory
+ * @throws IOException if an exception occurs in the creation/access of
+ * the specified file
+ * @throws RuntimeException if file at specified path contains data in a
+ * non-standard format
+ */
+ Storage(String filePath) throws IOException {
+ tasks = new ArrayList<>();
+ this.filePath = filePath;
+ File data = new File(filePath);
+ //make preceding directories, if any are not found
+ ignoreResult(data.getParentFile().mkdirs());
+
+ // If file does not exist, create new file and return
+ if (data.createNewFile()) {
+ assert data.exists() : "file should be created if it does not exist";
+ return;
+ }
+ assert data.exists() : "file should exist";
+
+ Scanner fileReader = new Scanner(data);
+ while (fileReader.hasNextLine()) {
+ String line = fileReader.nextLine();
+ String[] tmp = line.split("\\|");
+ boolean isDone = tmp[1].trim().equals("D");
+
+ switch (tmp[0].trim()) {
+ case "T":
+ assert tmp.length > 2 : "tmp[2] should exist";
+ todoHandler(tmp, isDone);
+ break;
+ case "D":
+ assert tmp.length > 3 : "tmp[2], tmp[3] should exist";
+ deadlineHandler(tmp, isDone);
+ break;
+ case "E":
+ assert tmp.length > 3 : "tmp[2], tmp[3] should exist";
+ eventHandler(tmp, isDone);
+ break;
+ case "A":
+ assert tmp.length > 3 : "tmp[2], tmp[3] should exist";
+ doAfterHandler(tmp, isDone);
+ break;
+ default:
+ throw new RuntimeException("Corrupted data in data file at "
+ + filePath);
+ }
+ }
+ fileReader.close();
+ }
+
+ /**
+ * Suppresses any unused warnings from a given boolean result.
+ *
+ * @param result variable to suppress warnings from
+ */
+ //@@author Eric Lange-reused
+ //Reused from https://stackoverflow.com/questions/27904329/warning-file-mkdir-is-ignored#answer-54341862
+ // with minor modifications
+ @SuppressWarnings("unused")
+ private static void ignoreResult(boolean result) {
+
+ }
+ //@@author
+
+ /**
+ * Adds Todo to tasks.
+ *
+ * @param arr array of data [Type, isDone, description]
+ * @param isDone bool indicating if Todo is marked complete
+ */
+ public void todoHandler(String[] arr, Boolean isDone) {
+ Todo t = new Todo(arr[2].trim());
+ if (isDone) {
+ t.markComplete();
+ assert t.getStatusIcon().equals("\u2713") : "task should be complete";
+ }
+ tasks.add(t);
+ }
+
+ /**
+ * Adds Deadline to tasks.
+ *
+ * @param arr array of data [Type, isDone, description, dateTime]
+ * @param isDone bool indicating if Deadline is marked complete
+ */
+ public void deadlineHandler(String[] arr, Boolean isDone) {
+ Deadline d = new Deadline(arr[2].trim(), arr[3].trim());
+ if (isDone) {
+ d.markComplete();
+ assert d.getStatusIcon().equals("\u2713") : "task should be complete";
+ }
+ tasks.add(d);
+ }
+
+ /**
+ * Adds Event to tasks.
+ *
+ * @param arr array of data [Type, isDone, description, dateTime]
+ * @param isDone bool indicating if Deadline is marked complete
+ */
+ public void eventHandler(String[] arr, Boolean isDone) {
+ Event e = new Event(arr[2].trim(), arr[3].trim());
+ if (isDone) {
+ e.markComplete();
+ assert e.getStatusIcon().equals("\u2713") : "task should be complete";
+ }
+ tasks.add(e);
+ }
+
+ /**
+ * Adds DoAfter to tasks.
+ *
+ * @param arr array of data [Type, isDone, description, dateTime]
+ * @param isDone bool indicating if Deadline is marked complete
+ */
+ public void doAfterHandler(String[] arr, Boolean isDone) {
+ DoAfter d = new DoAfter(arr[2].trim(), arr[3].trim());
+ if (isDone) {
+ d.markComplete();
+ assert d.getStatusIcon().equals("\u2713") : "task should be complete";
+ }
+ tasks.add(d);
+ }
+
+ /**
+ * Retrieves and returns Tasks saved from the storage file.
+ *
+ * @return ArrayList of saved Tasks
+ */
+ public ArrayList load() {
+ return tasks;
+ }
+
+ /**
+ * Updates the list of Tasks saved on hard-drive at filePath.
+ * This overwrites the existing file at filePath.
+ * Note: File creation is to be handled via load() prior this method as
+ * this assumes that filePath is valid and that the file exists at filePath.
+ *
+ * @param tasks TaskList of Tasks to be saved on hard-drive
+ * @throws IOException if an exception occurs in the saving of
+ * data to the file at filePath
+ */
+ public void save(TaskList tasks) throws IOException {
+ File data = new File(filePath);
+ assert data.exists() : "file was not created at start of program";
+ FileWriter f;
+
+ f = new FileWriter(data, false);
+ boolean isFirst = true;
+ for (int i = 0; i < tasks.size(); i++) {
+ String s = isFirst ? "" : "\n";
+ f.write(s + tasks.get(i).writeToFile());
+ isFirst = false;
+ }
+ f.close();
+ }
+}
diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/Ui.java
new file mode 100644
index 0000000000..301aa3a2df
--- /dev/null
+++ b/src/main/java/duke/Ui.java
@@ -0,0 +1,196 @@
+package duke;
+
+import duke.task.TaskList;
+
+/**
+ * Represents a UI class that deals with user input and application output.
+ *
+ * @author Elumalai Oviya Dharshini
+ * @version 1.1
+ */
+public class Ui {
+
+ /**
+ * Returns a welcome message.
+ *
+ * @return welcome message.
+ */
+ public static String showWelcome() {
+ return "Hello, traveller! I'm Paimon.\n"
+ + "How can I help you today?";
+ }
+
+ /**
+ * Returns a generic error message about failed data loading from a file.
+ *
+ * @return error message about data loading errors.
+ */
+ public static String showLoadingError() {
+ return "An error occurred with processing the data file";
+ }
+
+ /**
+ * Returns a specific error message about some exception in the program.
+ *
+ * @param s string describing error
+ * @return string describing error
+ */
+ public static String showError(String s) {
+ return s;
+ }
+
+ /**
+ * Returns an error handling message for empty strings.
+ */
+ public static String showEmptyMessage() {
+ return "Paimon cannot read minds!";
+ }
+
+ /**
+ * Returns string of list of tasks and number of tasks.
+ *
+ * @param tasks current TaskList
+ * @return formatted string of tasks
+ */
+ public static String showListMessage(TaskList tasks) {
+ StringBuilder str = new StringBuilder();
+ str.append("Hmm... Paimon keeps a clear record in her diary.\n")
+ .append(tasks)
+ .append("You now have ")
+ .append(tasks.size())
+ .append(" task")
+ .append(tasks.size() == 1 ? "" : "s")
+ .append(" on your list.");
+
+ return str.toString();
+ }
+
+ /**
+ * Returns message for task being marked incomplete.
+ *
+ * @return string stating that task is updated.
+ */
+ public static String showUndoMessage() {
+ return "...ah. The task has been updated accordingly.";
+ }
+
+ /**
+ * Returns message for task being completed.
+ *
+ * @return string stating that task is completed.
+ */
+ public static String showDoMessage() {
+ return "Great job! The task has been updated accordingly.";
+ }
+
+ /**
+ * Returns task deletion message and resulting list of tasks.
+ *
+ * @param tasks TaskList of tasks
+ * @return string of list of tasks and deletion message.
+ */
+ public static String showDeleteMessage(TaskList tasks) {
+ StringBuilder str = new StringBuilder();
+
+ str.append("Noted, the task has been scrubbed off the list!\n")
+ .append(tasks)
+ .append("You now have ")
+ .append(tasks.size())
+ .append(" task")
+ .append(tasks.size() == 1 ? "" : "s")
+ .append(" on your list.");
+
+ return str.toString();
+ }
+
+ /**
+ * Returns message for the addition of a Todo.
+ *
+ * @return string stating that a Todo task has been added.
+ */
+ public static String showTodoMessage() {
+ return "Got it! I have noted down the following task "
+ + "in your list.\n";
+ }
+
+ /**
+ * Returns generic message for finding tasks.
+ *
+ * @return string stating that matching tasks are found.
+ */
+ public static String showFindMessage(TaskList foundTasks) {
+ if (foundTasks.size() == 0) {
+ return "Paimon could not find any matching tasks.";
+ }
+ StringBuilder str = new StringBuilder();
+
+ str.append("Here are the matching tasks in your list:\n")
+ .append(foundTasks)
+ .append("There ")
+ .append(foundTasks.size() == 1 ? "is " : "are ")
+ .append(foundTasks.size())
+ .append(" matching task")
+ .append(foundTasks.size() == 1 ? "" : "s")
+ .append(" on your list.");
+
+ return str.toString();
+ }
+
+ /**
+ * Returns message for the addition of a Deadline task.
+ *
+ * @return string stating that a Deadline task has been added.
+ */
+ public static String showDeadlineMessage() {
+ return "Got it! I have noted down the following task in"
+ + " your list. \nRemember the deadline!\n";
+ }
+
+ /**
+ * Returns message for the addition of an Event task.
+ *
+ * @return string stating that an Event task has been added.
+ */
+ public static String showEventMessage() {
+ return "Got it! I have noted down the following task in"
+ + " your list.\nDo be there on time!\n";
+ }
+
+ /**
+ * Returns message for the addition of a DoAfter task.
+ *
+ * @return string stating that a DoAfter task has been added.
+ */
+ public static String showDoAfterMessage() {
+ return "Got it! I have noted down the following task in"
+ + " your list.\nDon't forget it!\n";
+ }
+
+ /**
+ * Returns message for the end of the program.
+ *
+ * @return string to end program.
+ */
+ public static String showByeMessage() {
+ return "Bye, hope to see you again soon!";
+ }
+
+ /**
+ * Returns generic message for indecipherable input.
+ *
+ * @return generic message about unclear input.
+ */
+ public static String showDefaultMessage() {
+ return "That went over Paimon's head a little...";
+ }
+
+ /**
+ * Returns generic message for incorrect number input.
+ *
+ * @return warning message about number input.
+ */
+ public static String showNumberFormatMessage() {
+ return "That went over Paimon's head a little...\n"
+ + "You need to type a number instead.";
+ }
+}
diff --git a/src/main/java/duke/gui/DialogBox.java b/src/main/java/duke/gui/DialogBox.java
new file mode 100644
index 0000000000..14a0c782ef
--- /dev/null
+++ b/src/main/java/duke/gui/DialogBox.java
@@ -0,0 +1,66 @@
+package duke.gui;
+
+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;
+import javafx.scene.shape.Circle;
+
+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);
+
+ double x = displayPicture.getX() + displayPicture.getFitWidth() / 2;
+ double y = displayPicture.getY() + displayPicture.getFitHeight() / 2;
+ double radius = Math.min(displayPicture.getFitWidth(),
+ displayPicture.getFitHeight()) / 2;
+ displayPicture.setClip(new Circle(x, y, radius));
+ }
+
+ /**
+ * 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/gui/Launcher.java b/src/main/java/duke/gui/Launcher.java
new file mode 100644
index 0000000000..57842f4834
--- /dev/null
+++ b/src/main/java/duke/gui/Launcher.java
@@ -0,0 +1,14 @@
+package duke.gui;
+
+import duke.Main;
+
+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/gui/MainWindow.java b/src/main/java/duke/gui/MainWindow.java
new file mode 100644
index 0000000000..98ffb673ee
--- /dev/null
+++ b/src/main/java/duke/gui/MainWindow.java
@@ -0,0 +1,129 @@
+package duke.gui;
+
+import duke.Duke;
+import duke.Expression;
+import duke.Response;
+import duke.Ui;
+
+import javafx.animation.PauseTransition;
+import javafx.application.Platform;
+
+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 javafx.util.Duration;
+
+/**
+ * Controller for MainWindow. Provides the layout for the other controls.
+ * Images used in this application are sourced from sticker packs from
+ * https://getstickerpack.com/stickers/genshin-impact-aether and
+ * https://getstickerpack.com/stickers/genshin-impact-paimon-1 by U-KEY.
+ *
+ */
+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/User.png"));
+ private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/DukeDefault.png"));
+
+ /**
+ * Initializes the main window.
+ */
+ @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();
+ Response r = duke.getResponse(input);
+
+ String response = r.getMessage();
+ Expression expression = r.getExpression();
+
+ dukeImage = getDukeImage(expression);
+
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialog(input, userImage),
+ DialogBox.getDukeDialog(response, dukeImage)
+ );
+ userInput.clear();
+
+ if (r.isExit()) {
+ PauseTransition end = new PauseTransition(Duration.seconds(1));
+ end.setOnFinished(a -> endProgram());
+ end.play();
+ }
+ }
+
+ /**
+ * Exit program.
+ */
+ private void endProgram() {
+ Platform.exit();
+ System.exit(0);
+ }
+
+ /**
+ * Displays welcome message.
+ */
+ private void showWelcomeMessage() {
+ String response = Ui.showWelcome();
+
+ dialogContainer.getChildren().addAll(
+ DialogBox.getDukeDialog(response, dukeImage)
+ );
+ userInput.clear();
+ }
+
+ /**
+ * Gets Duke's image depending on its expression.
+ * Idea is adapted from kev-intq(@github)'s GUI.
+ *
+ * @param expression Duke's expression
+ * @return image with expression.
+ */
+ private Image getDukeImage(Expression expression) {
+ String filePath = "/images/";
+
+ switch(expression) {
+ case DISAPPOINTED:
+ filePath += "DukeDisappointed.png";
+ break;
+ case HAPPY:
+ filePath += "DukeHappy.png";
+ break;
+ case THUMBSUP:
+ filePath += "DukeThumbsUp.png";
+ break;
+ default:
+ filePath += "DukeDefault.png";
+ break;
+ }
+
+ return new Image(this.getClass().getResourceAsStream(filePath));
+ }
+}
diff --git a/src/main/java/duke/task/Deadline.java b/src/main/java/duke/task/Deadline.java
new file mode 100644
index 0000000000..35903aae5d
--- /dev/null
+++ b/src/main/java/duke/task/Deadline.java
@@ -0,0 +1,47 @@
+package duke.task;
+
+/**
+ * Represents a task that occurs at a particular date and/or time.
+ * Note: the allowable formats for date and time to be interpreted
+ * correctly by Deadline is YYYY/MM/DD (with ./| being valid separators)
+ * and HHMM.
+ *
+ * @author Elumalai Oviya Dharshini
+ * @version 1.1
+ */
+public class Deadline extends TaskWithDateTime {
+
+ /**
+ * Constructor for Deadline specifying description, dateTime string.
+ *
+ * @param description description of Deadline
+ * @param by dateTime string associated with Deadline
+ */
+ public Deadline(String description, String by) {
+ super(description, by);
+ }
+
+ /**
+ * Default toString method that returns formatted Deadline.
+ *
+ * @return formatted string of the description, dateTime and completeness
+ * status of Deadline with a Deadline marker.
+ */
+ @Override
+ public String toString() {
+ String s = "[D]" + super.toString();
+ return s.replace(" (at: ", " (by: ");
+ }
+
+ /**
+ * Parses contents of Deadline into a csv-like format.
+ * Delimiter is '|'.
+ *
+ * @return formatted string of Deadline, its dateTime, completion status
+ * and a Deadline marker.
+ */
+ @Override
+ public String writeToFile() {
+ return "D | " + super.writeToFile();
+ }
+}
diff --git a/src/main/java/duke/task/DoAfter.java b/src/main/java/duke/task/DoAfter.java
new file mode 100644
index 0000000000..715c86eb94
--- /dev/null
+++ b/src/main/java/duke/task/DoAfter.java
@@ -0,0 +1,47 @@
+package duke.task;
+
+/**
+ * Represents a task that occurs at a particular date and/or time.
+ * Note: the allowable formats for date and time to be interpreted
+ * correctly by Deadline is YYYY/MM/DD (with ./| being valid separators)
+ * and HHMM.
+ *
+ * @author Elumalai Oviya Dharshini
+ * @version 1.1
+ */
+public class DoAfter extends TaskWithDateTime {
+
+ /**
+ * Constructor for DoAfter specifying description, dateTime string.
+ *
+ * @param description description of Deadline
+ * @param after dateTime string associated with Deadline
+ */
+ public DoAfter(String description, String after) {
+ super(description, after);
+ }
+
+ /**
+ * Default toString method that returns formatted Deadline.
+ *
+ * @return formatted string of the description, dateTime and completeness
+ * status of Deadline with a Deadline marker.
+ */
+ @Override
+ public String toString() {
+ String s = "[A]" + super.toString();
+ return s.replace(" (at: ", " (after: ");
+ }
+
+ /**
+ * Parses contents of Deadline into a csv-like format.
+ * Delimiter is '|'.
+ *
+ * @return formatted string of Deadline, its dateTime, completion status
+ * and a Deadline marker.
+ */
+ @Override
+ public String writeToFile() {
+ return "A | " + super.writeToFile();
+ }
+}
diff --git a/src/main/java/duke/task/Event.java b/src/main/java/duke/task/Event.java
new file mode 100644
index 0000000000..fe3a69a187
--- /dev/null
+++ b/src/main/java/duke/task/Event.java
@@ -0,0 +1,45 @@
+package duke.task;
+
+/**
+ * Represents a task that occurs at a particular date and/or time.
+ * Note: the allowable formats for date and time to be interpreted correctly
+ * by Event is YYYY/MM/DD (with * ./| being valid separators) and HHMM.
+ *
+ * @author Elumalai Oviya Dharshini
+ * @version 1.1
+ */
+public class Event extends TaskWithDateTime {
+
+ /**
+ * Constructor for Event specifying description, dateTime string.
+ *
+ * @param description description of Event
+ * @param at dateTime string associated with Event
+ */
+ public Event(String description, String at) {
+ super(description, at);
+ }
+
+ /**
+ * Default toString method that returns formatted Event.
+ *
+ * @return formatted string of the description, dateTime and completeness
+ * status of Event with an Event marker.
+ */
+ @Override
+ public String toString() {
+ return "[E]" + super.toString();
+ }
+
+ /**
+ * Parses contents of Event into a csv-like format.
+ * Delimiter is '|'.
+ *
+ * @return formatted string of Event, its dateTime, completion status
+ * and an Event marker.
+ */
+ @Override
+ public String writeToFile() {
+ return "E | " + super.writeToFile();
+ }
+}
diff --git a/src/main/java/duke/task/Task.java b/src/main/java/duke/task/Task.java
new file mode 100644
index 0000000000..5c82142abd
--- /dev/null
+++ b/src/main/java/duke/task/Task.java
@@ -0,0 +1,67 @@
+package duke.task;
+
+/**
+ * Represents a task and consists of its description and completeness status.
+ *
+ * @author Elumalai Oviya Dharshini
+ * @version 1.1
+ */
+public class Task {
+ private String description;
+ private boolean isDone;
+
+ /**
+ * Constructor for Task specifying description.
+ *
+ * @param description description of task
+ */
+ public Task(String description) {
+ this.description = description;
+ this.isDone = false;
+ }
+
+ /**
+ * Marks Task as complete.
+ */
+ public void markComplete() {
+ this.isDone = true;
+ }
+
+ /**
+ * Marks Task as incomplete.
+ * Note: this is true by default for all Task objects.
+ */
+ public void markIncomplete() {
+ this.isDone = false;
+ }
+
+ /**
+ * Returns the state of completeness of Task.
+ *
+ * @return "X" if Task is completed, " " otherwise.
+ */
+ public String getStatusIcon() {
+ return (isDone ? "\u2713" : " ");
+ }
+
+ /**
+ * Default toString method that returns the description of Task.
+ * Includes completion status.
+ *
+ * @return formatted string of the description and completeness status
+ * of Task.
+ */
+ public String toString() {
+ return "[" + this.getStatusIcon() + "] " + description;
+ }
+
+ /**
+ * Parses contents of Task into a csv-like format delimited by '|'.
+ *
+ * @return formatted string of Task and its completion status.
+ */
+ public String writeToFile() {
+ String s = (isDone) ? "D" : "N";
+ return s + " | " + 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..56183f8b0b
--- /dev/null
+++ b/src/main/java/duke/task/TaskList.java
@@ -0,0 +1,112 @@
+package duke.task;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a collection of tasks.
+ *
+ * @author Elumalai Oviya Dharshini
+ * @version 1.1
+ */
+public class TaskList {
+ private List tasks;
+
+ /**
+ * Empty constructor for TaskList.
+ */
+ public TaskList() {
+ this.tasks = new ArrayList<>();
+ }
+
+ /**
+ * Constructor for TaskList specifying a list of Tasks.
+ *
+ * @param tasks list of Tasks to be initialized
+ */
+ public TaskList(List tasks) {
+ this.tasks = new ArrayList<>();
+ this.tasks.addAll(tasks);
+ }
+
+ /**
+ * Returns the length of TaskList.
+ *
+ * @return number of elements in tasks.
+ */
+ public int size() {
+ return tasks.size();
+ }
+
+ /**
+ * Retrieves and returns Task at a given index in TaskList.
+ *
+ * @param i index of Task to be retrieved in TaskList
+ * @return Task at a given index in tasks.
+ */
+ public Task get(int i) {
+ if (i < size() && i >= 0) {
+ return tasks.get(i);
+ }
+ return null;
+ }
+
+ /**
+ * Deletes Task at a given index in TaskList.
+ *
+ * @param i index of task to be removed
+ */
+ public void remove(int i) {
+ if (i < size() && i >= 0) {
+ tasks.remove(i);
+ }
+ }
+
+ /**
+ * Appends Task to end of TaskList.
+ *
+ * @param t Task to be added
+ */
+ public void add(Task t) {
+ tasks.add(t);
+ }
+
+ /**
+ * Returns matching occurrences of a given word in tasks.
+ *
+ * @param word keyword to be searched for
+ * @return TaskList of the Tasks containing the specified word
+ */
+ public TaskList find(String word) {
+ ArrayList matchingTasks = new ArrayList<>();
+
+ for (Task task: tasks) {
+ if (task.toString().contains(word)) {
+ matchingTasks.add(task);
+ }
+ }
+
+ return new TaskList(matchingTasks);
+ }
+
+ /**
+ * Default toString method that returns list of all tasks.
+ *
+ * @return formatted string of all Tasks in TaskList delimited by newlines.
+ */
+ public String toString() {
+ StringBuilder str = new StringBuilder();
+
+ for (int i = 0; i < tasks.size(); i++) {
+ int index = i + 1;
+
+ str.append(" ")
+ .append(index)
+ .append(". ")
+ .append(tasks.get(i))
+ .append("\n");
+ }
+
+ return str.toString();
+ }
+}
diff --git a/src/main/java/duke/task/TaskWithDateTime.java b/src/main/java/duke/task/TaskWithDateTime.java
new file mode 100644
index 0000000000..08e56e375f
--- /dev/null
+++ b/src/main/java/duke/task/TaskWithDateTime.java
@@ -0,0 +1,120 @@
+package duke.task;
+
+import java.time.DateTimeException;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Represents a task with an associated date and/or time.
+ *
+ * Note: the allowable formats for date and time to be interpreted correctly
+ * by TaskWithDateTime is YYYY/MM/DD (with ./| being valid separators) and HHMM.
+ *
+ * @author Elumalai Oviya Dharshini
+ * @version 1.1
+ */
+public class TaskWithDateTime extends Task {
+ private String dateTime;
+ private String originalDateTime;
+
+ /**
+ * Constructor for TaskWithDateTime.
+ * Specifies description and dateTime.
+ *
+ * @param description description of task
+ * @param dateTime datetime associated with task in string format
+ */
+ public TaskWithDateTime(String description, String dateTime) {
+ super(description);
+
+ dateTime = dateTime.trim();
+ this.dateTime = dateTime;
+ this.originalDateTime = dateTime;
+
+ extractTime(extractDate());
+ }
+
+ /**
+ * Formats date in dateTime correctly (if any).
+ * Dates detected are of the format YYYYMMDD delimited by -|/.
+ * and are changed to MMM d yyyy format.
+ *
+ * @return string with date removed.
+ */
+ public String extractDate() {
+ String regexDate = "\\d{4}[-|/.]\\d{2}[-|/.]\\d{2}";
+ Matcher m = Pattern.compile(regexDate).matcher(dateTime);
+ String strWithoutDate = dateTime;
+
+ // If specified date format is found
+ if (m.find()) {
+ strWithoutDate = strWithoutDate.replace(m.group(0), "");
+
+ try {
+ LocalDate day = LocalDate.parse(m.group(0).replaceAll("[./|]", "-"));
+ dateTime = dateTime.replace(m.group(0),
+ day.format(DateTimeFormatter.ofPattern("MMM d yyyy")));
+ } catch (DateTimeException e) {
+ // Set date to the next day if date is invalid
+ dateTime = dateTime.replace(m.group(0),
+ LocalDate.now().plus(1, ChronoUnit.DAYS).toString());
+ }
+ }
+
+ return strWithoutDate;
+ }
+
+ /**
+ * Formats time in dateTime correctly (if any).
+ * Time detected is of the format HHMM, and is changed to HH:MM.
+ * Note: input string must not contain any dates or other four
+ * contiguous digits.
+ *
+ * @param str time string associated with task
+ */
+ public void extractTime(String str) {
+ String regexTime = "\\d{4}";
+ Matcher m = Pattern.compile(regexTime).matcher(str);
+
+ // If specified time format is found
+ if (m.find()) {
+ try {
+ LocalTime timeOfDay = LocalTime.parse(m.group(0).substring(0, 2)
+ + ":" + m.group(0).substring(2));
+ dateTime = dateTime.replace(m.group(0), timeOfDay.toString());
+ } catch (DateTimeException e) {
+ // Set time to an hour from now if time is invalid
+ dateTime = dateTime.replace(m.group(0),
+ LocalTime.now().plus(1, ChronoUnit.HOURS).toString());
+ }
+ }
+ }
+
+ /**
+ * Default toString method that returns formatted TaskWithDateTime.
+ *
+ * @return formatted string of description, dateTime and completion
+ * status of the TaskWithDateTime object.
+ */
+ @Override
+ public String toString() {
+ return super.toString() + " (at: " + dateTime.trim() + ")";
+ }
+
+ /**
+ * Parses contents of TaskWithDateTime into a csv-like format.
+ * Delimiter is '|'.
+ *
+ * @return formatted string of TaskWithDateTime, its completion
+ * status and associated dateTime.
+ */
+ @Override
+ public String writeToFile() {
+ return super.writeToFile() + " | " + originalDateTime;
+ }
+}
diff --git a/src/main/java/duke/task/Todo.java b/src/main/java/duke/task/Todo.java
new file mode 100644
index 0000000000..3dfcbb3130
--- /dev/null
+++ b/src/main/java/duke/task/Todo.java
@@ -0,0 +1,41 @@
+package duke.task;
+
+/**
+ * Represents a todo - a task with a description and no time limit.
+ *
+ * @author Elumalai Oviya Dharshini
+ * @version 1.1
+ */
+public class Todo extends Task {
+
+ /**
+ * Constructor for Todo specifying Task with description.
+ *
+ * @param description description of Todo
+ */
+ public Todo(String description) {
+ super(description);
+ }
+
+ /**
+ * Default toString method that returns the description of Todo.
+ * Includes completion status and Todo marker.
+ *
+ * @return formatted string of the description and completeness
+ * status of Todo with a Todo marker.
+ */
+ @Override
+ public String toString() {
+ return "[T]" + super.toString();
+ }
+
+ /**
+ * Parses contents of Todo into a csv-like format delimited by '|'.
+ *
+ * @return formatted string of Todo, its completion status and Todo marker
+ */
+ @Override
+ public String writeToFile() {
+ return "T | " + super.writeToFile();
+ }
+}
diff --git a/src/main/resources/images/DukeDefault.png b/src/main/resources/images/DukeDefault.png
new file mode 100644
index 0000000000..8796aaf73b
Binary files /dev/null and b/src/main/resources/images/DukeDefault.png differ
diff --git a/src/main/resources/images/DukeDisappointed.png b/src/main/resources/images/DukeDisappointed.png
new file mode 100644
index 0000000000..20dd6b004f
Binary files /dev/null and b/src/main/resources/images/DukeDisappointed.png differ
diff --git a/src/main/resources/images/DukeThumbsUp.png b/src/main/resources/images/DukeThumbsUp.png
new file mode 100644
index 0000000000..36caaf14c9
Binary files /dev/null and b/src/main/resources/images/DukeThumbsUp.png differ
diff --git a/src/main/resources/images/Dukehappy.png b/src/main/resources/images/Dukehappy.png
new file mode 100644
index 0000000000..5a7fb7d7ec
Binary files /dev/null and b/src/main/resources/images/Dukehappy.png differ
diff --git a/src/main/resources/images/Icon.png b/src/main/resources/images/Icon.png
new file mode 100644
index 0000000000..eb8311a19e
Binary files /dev/null and b/src/main/resources/images/Icon.png differ
diff --git a/src/main/resources/images/User.png b/src/main/resources/images/User.png
new file mode 100644
index 0000000000..b7ddb51276
Binary files /dev/null and b/src/main/resources/images/User.png differ
diff --git a/src/main/resources/style.css b/src/main/resources/style.css
new file mode 100644
index 0000000000..e7f14eaeae
--- /dev/null
+++ b/src/main/resources/style.css
@@ -0,0 +1,11 @@
+.label {
+ -fx-font-family: 'Open Sans', sans-serif;
+ -fx-font-size: 12;
+ -fx-border-radius: 5 5 5 5;
+ -fx-background-radius: 5 5 5 5;
+ -fx-background-color: #e8e8e8;
+
+ -fx-padding: 10px;
+ -fx-border-insets: 5px;
+ -fx-background-insets: 5px;
+}
\ No newline at end of file
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 0000000000..e7fb9db365
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
new file mode 100644
index 0000000000..8c26537855
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/duke/ParserTest.java b/src/test/java/duke/ParserTest.java
new file mode 100644
index 0000000000..30f55d6049
--- /dev/null
+++ b/src/test/java/duke/ParserTest.java
@@ -0,0 +1,22 @@
+package duke;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class ParserTest {
+ @Test
+ public void testParsing() {
+ assertEquals("", Parser.parse("hi"));
+ assertEquals("", Parser.parse("todo"));
+ assertEquals("", Parser.parse("todo "));
+ assertEquals("", Parser.parse("event "));
+ assertEquals("", Parser.parse("event birthday at"));
+ assertEquals("", Parser.parse("event birthday at "));
+ assertEquals("event", Parser.parse("event birthday at 1500"));
+ assertEquals("", Parser.parse("deadline cs2103t week 3 ip tasks by"));
+ assertEquals("", Parser.parse("deadline cs2103t week 3 ip tasks by "));
+ assertEquals("deadline", Parser.parse("deadline cs2103t week 3 ip tasks by 2022/01/27 2359"));
+ }
+}
diff --git a/src/test/java/duke/task/DeadlineTest.java b/src/test/java/duke/task/DeadlineTest.java
new file mode 100644
index 0000000000..95ed17e362
--- /dev/null
+++ b/src/test/java/duke/task/DeadlineTest.java
@@ -0,0 +1,20 @@
+package duke.task;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class DeadlineTest {
+ @Test
+ public void testStringConversion() {
+ Deadline d = new Deadline("read a book","2022/12/01 2359");
+ assertEquals("[D][ ] read a book (by: Dec 1 2022 23:59)", d.toString());
+ }
+
+ @Test
+ public void testWriteToFile() {
+ Deadline d = new Deadline("read a book","2022/12/01 2359");
+ d.markComplete();
+ assertEquals("D | D | read a book | Dec 1 2022 23:59", d.writeToFile());
+ }
+}
diff --git a/src/test/java/duke/task/EventTest.java b/src/test/java/duke/task/EventTest.java
new file mode 100644
index 0000000000..b4535c216c
--- /dev/null
+++ b/src/test/java/duke/task/EventTest.java
@@ -0,0 +1,20 @@
+package duke.task;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class EventTest {
+ @Test
+ public void testStringConversion() {
+ Event e = new Event("read a book","2022/12/01 2359");
+ assertEquals("[E][ ] read a book (at: Dec 1 2022 23:59)", e.toString());
+ }
+
+ @Test
+ public void testWriteToFile() {
+ Event e = new Event("read a book","2022/12/01 2359");
+ e.markComplete();
+ assertEquals("E | D | read a book | Dec 1 2022 23:59", e.writeToFile());
+ }
+}
diff --git a/src/test/java/duke/task/TodoTest.java b/src/test/java/duke/task/TodoTest.java
new file mode 100644
index 0000000000..c008caecb9
--- /dev/null
+++ b/src/test/java/duke/task/TodoTest.java
@@ -0,0 +1,20 @@
+package duke.task;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class TodoTest {
+ @Test
+ public void testStringConversion() {
+ assertEquals("[T][ ] read a book", new Todo("read a book").toString());
+ }
+
+ @Test
+ public void testWriteToFile() {
+ Todo t = new Todo("read a book");
+ t.markComplete();
+ assertEquals("T | D | read a book", t.writeToFile());
+ }
+
+}
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 657e74f6e7..6c8e4416ea 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,7 +1,31 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
-
+Hello, traveller! My name in Paimon.
+How can I help you today?
+|
Got it! I have noted down the following task to your list.
+[T][ ] take down Stormterror
+|
Hmm... Paimon keeps a clear record in her diary.
+ 1. [T][ ] take down Stormterror
+|
Got it! I have noted down the following task to your list.
+Do be there on time!
+[E][ ] windtrace festival (at: last week of January)
+|
Hmm... Paimon keeps a clear record in her diary.
+ 1. [T][ ] take down Stormterror
+ 2. [E][ ] windtrace festival (at: last week of January)
+|
Got it! I have noted down the following task to your list.
+Remember the deadline!
+[D][ ] pull for Shenqi (by: 25 Jan 2022)
+|
Hmm... Paimon keeps a clear record in her diary.
+ 1. [T][ ] take down Stormterror
+ 2. [E][ ] windtrace festival (at: last week of January)
+ 3. [D][ ] pull for Shenqi (by: 25 Jan 2022)
+|
Task successfully updated.
+|
Task successfully updated.
+|
Task successfully updated.
+|
Hmm... Paimon keeps a clear record in her diary.
+ 1. [T][X] take down Stormterror
+ 2. [E][X] windtrace festival (at: last week of January)
+ 3. [D][X] pull for Shenqi (by: 25 Jan 2022)
+|
|
Hmm... Paimon keeps a clear record in her diary.
+ 1. [T][ ] take down Stormterror
+ 2. [E][X] windtrace festival (at: last week of January)
+ 3. [D][X] pull for Shenqi (by: 25 Jan 2022)
+|
Bye, hope to see you again soon!
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index e69de29bb2..89c32c8356 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -0,0 +1,13 @@
+todo take down Stormterror
+list
+event windtrace festival at last week of January
+list
+deadline pull for Shenqi by 25 Jan 2022
+list
+do 1
+do 2
+do 3
+list
+undo 1
+list
+bye
\ 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