diff --git a/README.md b/README.md
index 8715d4d915..0f2208ab64 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Duke project template
+# duke.Duke project template
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.
@@ -13,7 +13,7 @@ Prerequisites: JDK 11, update Intellij to the most recent version.
1. If there are any further prompts, accept the defaults.
1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
In the same dialog, set the **Project language level** field to the `SDK default` option.
-3. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output:
+3. After that, locate the `src/main/java/duke.Duke.java` file, right-click it, and choose `Run duke.Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output:
```
Hello from
____ _
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..c5f814075e
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,57 @@
+plugins {
+ id 'java'
+ id 'application'
+ id 'com.github.johnrengelman.shadow' version '5.1.0'
+ id 'org.openjfx.javafxplugin' version '0.0.11'
+}
+
+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
+}
+
+run{
+ standardInput = System.in
+}
diff --git a/docs/README.md b/docs/README.md
index 8077118ebe..e00dd8b7cf 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,29 +1,142 @@
# User Guide
-## Features
+Duke is a **desktop app for managing tasks, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, Duke can get your tasks management done faster than traditional GUI apps.
-### Feature-ABC
+## Quick start
-Description of the feature.
+1. Ensure you have Java `11` or above installed in your Computer.
-### Feature-XYZ
+1. Download the latest `duke.jar` from [here](https://github.com/kenzantonius/ip/releases).
-Description of the feature.
+1. Copy the file to the folder you want to use as the _home folder_ for your Duke.
-## Usage
+1. Double click the jar file to run the application.
+ A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+ 
-### `Keyword` - Describe action
+1. Type the command in the command box and press Enter to execute it.
+ Some example commands you can try:
-Describe the action and its outcome.
+ * `list` : Lists all tasks.
-Example of usage:
+ * `delete 3` : Deletes the 3rd task shown in the task list.
-`keyword (optional arguments)`
+ * `mark 3` : Mark the 3rd task shown in the task list.
-Expected outcome:
+ * `bye` : Exits the app.
-Description of the outcome.
+1. Refer to the [Features](#features) below for details of each command.
-```
-expected output
-```
+--------------------------------------------------------------------------------------------------------------------
+
+## Features
+
+### Adding a todo task: `todo`
+
+Adds a todo task to the task list.
+
+Format: `todo TASK_NAME`
+
+Examples:
+* `todo borrow book`
+
+### Adding a deadline task: `deadline`
+
+Adds a event task to the task list.
+
+Format: `deadline TASK_NAME /by DUE_DATE`
+
+Examples:
+* `deadline return book /by Sunday`
+
+### Adding a event task: `event`
+
+Adds a event task to the task list.
+
+Format: `event TASK_NAME /from START_DATE /to END_DATE`
+
+Examples:
+* `event project meeting /from Mon 2pm /to 4pm`
+
+### Mark a task : `mark`
+
+Marks the specified task from the task list.
+
+Format: `mark INDEX`
+
+* Deletes the task at the specified `INDEX`.
+* The index refers to the index number shown in the displayed task list.
+* The index **must be a positive integer** 1, 2, 3, …
+
+Examples:
+* `mark 2` marks the 2nd task in the task list.
+
+### Unmark a task : `unmark`
+
+Unmarks the specified task from the task list.
+
+Format: `unmark INDEX`
+
+* Deletes the task at the specified `INDEX`.
+* The index refers to the index number shown in the displayed task list.
+* The index **must be a positive integer** 1, 2, 3, …
+
+Examples:
+* `unmark 2` unmarks the 2nd task in the task list.
+
+### Listing all tasks : `list`
+
+Shows a list of all tasks in the task list.
+
+Format: `list`
+
+### Locating tasks by keyword: `find`
+
+Finds tasks whose tasks contain any of the given keywords.
+
+Format: `find KEYWORD`
+
+Examples:
+* `find John` returns tasks that have a keyword `John`
+
+### Deleting a task : `delete`
+
+Deletes the specified task from the task list.
+
+Format: `delete INDEX`
+
+* Deletes the task at the specified `INDEX`.
+* The index refers to the index number shown in the displayed task list.
+* The index **must be a positive integer** 1, 2, 3, …
+
+Examples:
+* `delete 2` deletes the 2nd task in the task list.
+
+### Exiting the program : `bye`
+
+Exits the program.
+
+Format: `bye`
+
+### Saving the data
+
+Duke data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+
+### Editing the data file
+
+Duke data are saved as a txt file `[JAR file location]/data/duke.txt`. Advanced users are welcome to update data directly by editing that data file.
+
+
:exclamation: **Caution:**
+If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run.
+
+
+### Archiving data files `[coming in v2.0]`
+
+_Details coming soon ..._
+
+--------------------------------------------------------------------------------------------------------------------
+
+## FAQ
+
+**Q**: How do I transfer my data to another Computer?
+**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Duke home folder.
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..a27f8b44bd
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/duke/DialogBox.java b/src/main/java/duke/DialogBox.java
new file mode 100644
index 0000000000..d183f8252b
--- /dev/null
+++ b/src/main/java/duke/DialogBox.java
@@ -0,0 +1,61 @@
+package duke;
+
+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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java
new file mode 100644
index 0000000000..a7dd18805a
--- /dev/null
+++ b/src/main/java/duke/Duke.java
@@ -0,0 +1,48 @@
+package duke;
+
+import javafx.stage.Stage;
+import duke.command.Command;
+import duke.task.TaskList;
+
+public class Duke {
+ private Storage storage;
+ private TaskList tasks;
+ private Ui ui;
+
+ public Duke(Stage stage) {
+ storage = new Storage("data/tasks.txt");
+ ui = new Ui(stage);
+
+ try {
+ tasks = new TaskList(storage.load());
+ } catch (DukeException e) {
+ Ui.showLoadingError();
+ tasks = new TaskList();
+ }
+ }
+
+ private void runDuke() {
+ Ui.showGreeting();
+
+ boolean isExit = false;
+ while (!isExit) {
+ try {
+ String Command = ui.readCommand();
+ Command c = Parser.parse(Command);
+ c.execute(tasks, ui, storage);
+ isExit = c.isExit();
+ } catch (DukeException e) {
+ ui.showError(e.getMessage());
+ }
+ }
+ }
+
+ public String getResponse(String input) throws DukeException {
+ try {
+ Command c = Parser.parse(input);
+ return c.execute(tasks, ui, storage);
+ } catch (DukeException e){
+ return e.getMessage();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/DukeException.java b/src/main/java/duke/DukeException.java
new file mode 100644
index 0000000000..761c3c4410
--- /dev/null
+++ b/src/main/java/duke/DukeException.java
@@ -0,0 +1,7 @@
+package duke;
+
+public class DukeException extends Exception {
+ public DukeException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java
new file mode 100644
index 0000000000..035518dd2d
--- /dev/null
+++ b/src/main/java/duke/Launcher.java
@@ -0,0 +1,9 @@
+package duke;
+
+import javafx.application.Application;
+
+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..5a2fa071d8
--- /dev/null
+++ b/src/main/java/duke/Main.java
@@ -0,0 +1,32 @@
+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;
+
+/**
+ * A GUI for Duke using FXML.
+ */
+public class Main extends Application {
+
+ private Duke duke;
+
+ @Override
+ public void start(Stage stage) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml"));
+ AnchorPane ap = fxmlLoader.load();
+ Scene scene = new Scene(ap);
+ stage.setScene(scene);
+ duke = new Duke(stage);
+ fxmlLoader.getController().setDuke(duke);
+ stage.show();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/MainWindow.java b/src/main/java/duke/MainWindow.java
new file mode 100644
index 0000000000..3095436828
--- /dev/null
+++ b/src/main/java/duke/MainWindow.java
@@ -0,0 +1,53 @@
+package duke;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TextField;
+import javafx.scene.image.Image;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.VBox;
+/**
+ * Controller for MainWindow. Provides the layout for the other controls.
+ */
+public class MainWindow extends AnchorPane {
+ @FXML
+ private ScrollPane scrollPane;
+ @FXML
+ private VBox dialogContainer;
+ @FXML
+ private TextField userInput;
+ @FXML
+ private Button sendButton;
+
+ private Duke duke;
+
+ private Image userImage = new Image(this.getClass().getResourceAsStream("/images/DaUser.png"));
+ private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png"));
+
+ @FXML
+ public void initialize() {
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+ String welcome = Ui.showGreeting();
+ dialogContainer.getChildren().add(DialogBox.getDukeDialog(welcome, dukeImage));
+ }
+
+ 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() throws DukeException {
+ String input = userInput.getText();
+ String response = duke.getResponse(input);
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialog(input, userImage),
+ DialogBox.getDukeDialog(response, dukeImage)
+ );
+ userInput.clear();
+ }
+}
\ 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..c9aaef3578
--- /dev/null
+++ b/src/main/java/duke/Parser.java
@@ -0,0 +1,185 @@
+package duke;
+
+import duke.command.AddDeadlineCommand;
+import duke.command.AddEventCommand;
+import duke.command.AddTodoCommand;
+import duke.command.Command;
+import duke.command.DeleteCommand;
+import duke.command.ExitCommand;
+import duke.command.FindCommand;
+import duke.command.ListCommand;
+import duke.command.MarkCommand;
+import duke.command.UnmarkCommand;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class Parser {
+ public static Command parse(String Command) throws DukeException {
+ if (isExitCommand(Command)) {
+ return new ExitCommand();
+ } else if (isListCommand(Command)) {
+ return new ListCommand();
+ } else if (isMarkCommand(Command)) {
+ String userInput = Command.replaceFirst("mark", "").trim();
+ int taskNum = errorMarkIndex(userInput);
+ return new MarkCommand(taskNum - 1);
+ } else if (isUnmarkCommand(Command)) {
+ String userInput = Command.replaceFirst("unmark", "").trim();
+ int taskNum = errorUnmarkIndex(userInput);
+ return new UnmarkCommand(taskNum - 1);
+ } else if (isAddTodoCommand(Command)) {
+ String todo = Command.replaceFirst("todo", "").trim();
+ errorTodo(todo);
+ return new AddTodoCommand(todo);
+ } else if (isAddEventCommand(Command)) {
+ String[] event = errorEvent(Command);
+ if (isDate(event[1])) {
+ Date eventDate = parseDate(event[1]);
+ return new AddEventCommand(event[0], eventDate);
+ } else {
+ return new AddEventCommand(event[0], event[1]);
+ }
+ } else if (isAddDeadlineCommand(Command)) {
+ String[] deadline = errorDeadline(Command);
+ if (isDate(deadline[1])) {
+ Date deadlineDate = parseDate(deadline[1]);
+ return new AddDeadlineCommand(deadline[0], deadlineDate);
+ } else {
+ return new AddDeadlineCommand(deadline[0], deadline[1]);
+ }
+ } else if (isDeleteCommand(Command)) {
+ String userInput = Command.replaceFirst("delete", "").trim();
+ int taskNum = errorDeleteIndex(userInput);
+ return new DeleteCommand(taskNum);
+ } else if (isFindCommand(Command)){
+ String userInput = Command.replaceFirst("find", "").trim();
+ errorFind(userInput);
+ return new FindCommand(userInput);
+ } else {
+ throw new DukeException("☹ OOPS!!! I'm sorry, but I don't know what that means :-(");
+ }
+ }
+
+ public static Date parseDate(String date) throws DukeException {
+ try {
+ SimpleDateFormat dateFormatter = new SimpleDateFormat("dd/MM/yyyy HHmm");
+ return dateFormatter.parse(date);
+ } catch (ParseException e) {
+ throw new DukeException("An error occurred while parsing date: " + e);
+ }
+ }
+
+ private static boolean isDate(String input) {
+ String[] splitInput = input.split("/");
+ if (splitInput.length != 3 || !isNumeric(splitInput[0]) || !isNumeric(splitInput[1])) {
+ return false;
+ }
+
+ String[] yearAndTime = splitInput[2].split(" ");
+ if (yearAndTime.length != 2 || !isNumeric(yearAndTime[0]) || !isNumeric(yearAndTime[1])) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static int errorMarkIndex(String userInput) throws DukeException {
+ return errorMarkOrUnmarkOrDeleteIndex(userInput);
+ }
+
+ private static int errorUnmarkIndex(String userInput) throws DukeException {
+ return errorMarkOrUnmarkOrDeleteIndex(userInput);
+ }
+
+ private static int errorDeleteIndex(String userInput) throws DukeException {
+ return errorMarkOrUnmarkOrDeleteIndex(userInput);
+ }
+
+ private static int errorMarkOrUnmarkOrDeleteIndex(String userInput) throws DukeException {
+ if (userInput.isEmpty() || isNotNumeric(userInput)) {
+ throw new DukeException("☹ OOPS!!! The content of find cannot be empty.");
+ }
+
+ return Integer.parseInt(userInput);
+ }
+
+ private static void errorFind(String userInput) throws DukeException {
+ if (userInput.isEmpty()) {
+ throw new DukeException("☹ OOPS!!! The index to remove cannot be empty or not an integer.");
+ }
+ }
+
+ private static boolean isNotNumeric(String input) {
+ return !input.matches("-?\\d+(\\.\\d+)?");
+ }
+
+ private static void errorTodo(String todo) throws DukeException {
+ if (todo.isEmpty()) {
+ throw new DukeException("☹ OOPS!!! The description of a todo cannot be empty.");
+ }
+ }
+
+ private static String[] errorEvent(String userInput) throws DukeException {
+ return errorEventOrDeadline(userInput, "event", "/from");
+ }
+
+ private static String[] errorDeadline(String UserInput) throws DukeException {
+ return errorEventOrDeadline(UserInput, "deadline", "/by");
+ }
+
+ private static String[] errorEventOrDeadline(String UserInput, String textToReplace, String textToSplit) throws DukeException {
+ String[] splitInput = UserInput.replaceFirst(textToReplace, "")
+ .trim().split(textToSplit);
+
+ for (int i = 0; i < splitInput.length; i++) {
+ splitInput[i] = splitInput[i].trim();
+ }
+
+ if (splitInput.length != 2 || splitInput[0].isBlank() || splitInput[1].isBlank()) {
+ throw new DukeException("☹ OOPS!!! Please make sure the date is not empty");
+ }
+ return splitInput;
+ }
+
+ private static boolean isNumeric(String UserInput) {
+ return UserInput.matches("-?\\d+(\\.\\d+)?");
+ }
+
+ public static boolean isExitCommand(String UserInput) {
+ return UserInput.startsWith("bye");
+ }
+
+ public static boolean isListCommand(String UserInput) {
+ return UserInput.startsWith("list");
+ }
+
+ public static boolean isMarkCommand(String UserInput) {
+ return UserInput.startsWith("mark");
+ }
+
+ public static boolean isUnmarkCommand(String UserInput) {
+ return UserInput.startsWith("unmark");
+ }
+
+ public static boolean isAddTodoCommand(String UserInput) {
+ return UserInput.startsWith("todo");
+ }
+
+ public static boolean isAddDeadlineCommand(String UserInput) {
+ return UserInput.startsWith("deadline");
+ }
+
+ public static boolean isAddEventCommand(String UserInput) {
+ return UserInput.startsWith("event");
+ }
+
+ public static boolean isDeleteCommand(String UserInput) {
+ return UserInput.startsWith("delete");
+ }
+
+ public static boolean isFindCommand(String UserInput) {
+ return UserInput.startsWith("find");
+ }
+}
diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/Storage.java
new file mode 100644
index 0000000000..2e5e974518
--- /dev/null
+++ b/src/main/java/duke/Storage.java
@@ -0,0 +1,80 @@
+package duke;
+
+import duke.task.Deadline;
+import duke.task.Event;
+import duke.task.Task;
+import duke.task.Todo;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import java.util.ArrayList;
+public class Storage {
+ private String filePath;
+
+ Storage(String filePath) {
+ this.filePath = filePath;
+ }
+ ArrayList load() throws DukeException {
+ ArrayList tasks = new ArrayList<>();
+
+ try {
+ File file = new File(filePath);
+ if (!file.exists()) {
+ return tasks;
+ }
+
+ BufferedReader br = new BufferedReader(new FileReader(file));
+ String input;
+ while ((input = br.readLine()) != null) {
+ String[] splitInput = input.split(" \\| ");
+
+ Task task;
+ switch (splitInput[0]) {
+ case "T":
+ task = new Todo(splitInput[2]);
+ break;
+ case "D":
+ task = new Deadline(splitInput[2], splitInput[3]);
+ break;
+ case "E":
+ task = new Event(splitInput[2], splitInput[3]);
+ break;
+ default:
+ throw new DukeException("An error occurred during file parsing, unexpected task type was encountered.");
+ }
+
+ if (Integer.parseInt(splitInput[1]) == 1) {
+ task.markAsDone();
+ }
+ tasks.add(task);
+ }
+ } catch (IOException e) {
+ throw new DukeException("An IOException occurred. " + e);
+ } catch (NumberFormatException e) {
+ throw new DukeException("An error occurred during file parsing, unexpected done value encountered.");
+ }
+
+ return tasks;
+ }
+
+ public void save(ArrayList list) throws DukeException {
+ try {
+ File file = new File(filePath);
+ file.getParentFile().mkdirs();
+
+ BufferedWriter bw = new BufferedWriter(new FileWriter(file));
+ for (Task l : list) {
+ bw.append(l.getOutputFormat());
+ bw.append("\n");
+ }
+ bw.close();
+ } catch (IOException e) {
+ throw new DukeException("An IOException occurred. " + e);
+ }
+ }
+}
diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/Ui.java
new file mode 100644
index 0000000000..7c0cf530d7
--- /dev/null
+++ b/src/main/java/duke/Ui.java
@@ -0,0 +1,79 @@
+package duke;
+
+import duke.task.Task;
+
+import javafx.animation.PauseTransition;
+import javafx.stage.Stage;
+import javafx.util.Duration;
+import java.util.Scanner;
+import java.util.ArrayList;
+
+public class Ui {
+ static Stage stage;
+
+ public Ui(Stage stage) {
+ this.stage = stage;
+ }
+
+ public static String showAddMessage(Task taskNum, int size) {
+ String result = "\tGot it. I've added this task:\n";
+ result += ("\t\t" + taskNum);
+ result += ("\tNow you have " + size + " tasks in the list.");
+ return result;
+ }
+
+ public static String showDeleteMessage(Task taskNum, int size) {
+ String result = "\tNoted. I've removed this task:\n";
+ result += ("\t\t" + taskNum);
+ result += ("\tNow you have " + size + " tasks in the list.");
+ return result;
+ }
+
+ public static String showList(ArrayList tasks) {
+ String result = "\tHere are the tasks in your list:\n";
+ for (int i = 0; i < tasks.size(); i++) {
+ int taskNum = i + 1;
+ result += String.format("\t%d. %s\n", taskNum, tasks.get(i));
+ }
+ return result;
+ }
+
+ public static String showFind(ArrayList tasks) {
+ String result = ("\tHere are the matching tasks in your list:\n");
+ for (int i = 0; i < tasks.size(); i++) {
+ int taskNum = i + 1;
+ result += String.format("\t%d. %s\n", taskNum, tasks.get(i));
+ }
+ return result;
+ }
+
+ public String readCommand() {
+ Scanner scanner = new Scanner(System.in);
+ return scanner.nextLine();
+ }
+ public String showError(String msg) {
+ return(msg);
+ }
+
+ public static String showLoadingError() {
+ String result = ("\tCannot load file.\n");
+ return result;
+ }
+
+ public static String showGreeting() {
+ String result = ("Hello! I'm Duke\n\tWhat can I do for you?\n");
+ return result;
+ }
+
+ public static String showExit() {
+ PauseTransition delay = new PauseTransition(Duration.seconds(1.5));
+ delay.setOnFinished(event -> stage.close());
+ delay.play();
+ String result = ("\tBye. Hope to see you again soon!\n");
+ return result;
+ }
+
+ public static String showMessage(String msg) {
+ return(msg);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/command/AddDeadlineCommand.java b/src/main/java/duke/command/AddDeadlineCommand.java
new file mode 100644
index 0000000000..d8e1313017
--- /dev/null
+++ b/src/main/java/duke/command/AddDeadlineCommand.java
@@ -0,0 +1,61 @@
+package duke.command;
+
+import duke.Ui;
+import duke.Storage;
+import duke.DukeException;
+import duke.task.Deadline;
+import duke.task.TaskList;
+
+import java.util.Date;
+
+/**
+ * Adds a Deadline type task.
+ */
+public class AddDeadlineCommand extends Command {
+ public String description;
+ public String deadlineTime;
+ public Date dueDate;
+
+ /**
+ * Main constructor
+ *
+ * @param description description of the task.
+ * @param deadlineTime string of the due date.
+ */
+ public AddDeadlineCommand(String description, String deadlineTime) {
+ this.description = description;
+ this.deadlineTime = deadlineTime;
+ }
+
+ /**
+ * Main constructor
+ *
+ * @param description description of the task.
+ * @param dueDate due date of the event.
+ */
+ public AddDeadlineCommand(String description, Date dueDate) {
+ this.description = description;
+ this.dueDate = dueDate;
+ }
+
+ /**
+ * Adds a Deadline type task.
+ * Ask the UI to print the output.
+ *
+ * @param tasks Task list.
+ * @param ui UI of the application to interact with users.
+ * @param storage Storage to update when there is an update with the task list.
+ */
+ @Override
+ public String execute(TaskList tasks, Ui ui, Storage storage) throws DukeException {
+ Deadline deadline;
+ if (dueDate == null) {
+ deadline = new Deadline(description, deadlineTime);
+ } else {
+ deadline = new Deadline(description, dueDate);
+ }
+ tasks.add(deadline);
+ storage.save(tasks.getAllTasks());
+ return Ui.showAddMessage(deadline, tasks.size());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/command/AddEventCommand.java b/src/main/java/duke/command/AddEventCommand.java
new file mode 100644
index 0000000000..f0a832f98a
--- /dev/null
+++ b/src/main/java/duke/command/AddEventCommand.java
@@ -0,0 +1,61 @@
+package duke.command;
+
+import duke.Ui;
+import duke.Storage;
+import duke.DukeException;
+import duke.task.Event;
+import duke.task.TaskList;
+
+import java.util.Date;
+
+/**
+ * Adds an Event type task.
+ */
+public class AddEventCommand extends Command {
+ private String description;
+ private String eventAt;
+ private Date eventDate;
+
+ /**
+ * Main constructor
+ *
+ * @param description description of the task.
+ * @param deadlineBy string of the deadline date.
+ */
+ public AddEventCommand(String description, String deadlineBy) {
+ this.description = description;
+ this.eventAt = deadlineBy;
+ }
+
+ /**
+ * Main constructor
+ *
+ * @param description description of the task.
+ * @param deadlineDate deadline date of the event.
+ */
+ public AddEventCommand(String description, Date deadlineDate) {
+ this.description = description;
+ this.eventDate = deadlineDate;
+ }
+
+ /**
+ * Adds an Event type task.
+ * Ask the UI to print the output.
+ *
+ * @param tasks Task list.
+ * @param ui UI of the application to interact with users.
+ * @param storage Storage to update when there is an update with the task list.
+ */
+ @Override
+ public String execute(TaskList tasks, Ui ui, Storage storage) throws DukeException {
+ Event event;
+ if (eventDate == null) {
+ event = new Event(description, eventAt);
+ } else {
+ event = new Event(description, eventDate);
+ }
+ tasks.add(event);
+ storage.save(tasks.getAllTasks());
+ return Ui.showAddMessage(event, tasks.size());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/command/AddTodoCommand.java b/src/main/java/duke/command/AddTodoCommand.java
new file mode 100644
index 0000000000..4a48ed0349
--- /dev/null
+++ b/src/main/java/duke/command/AddTodoCommand.java
@@ -0,0 +1,39 @@
+package duke.command;
+
+import duke.Ui;
+import duke.Storage;
+import duke.DukeException;
+import duke.task.TaskList;
+import duke.task.Todo;
+
+/**
+ * Adds a Todo type task.
+ */
+public class AddTodoCommand extends Command {
+ private String description;
+
+ /**
+ * Main constructor
+ *
+ * @param description description of the task.
+ */
+ public AddTodoCommand(String description) {
+ this.description = description;
+ }
+
+ /**
+ * Adds a Todo type task.
+ * Ask UI to print the output.
+ *
+ * @param tasks Task list.
+ * @param ui UI of the application to interact with users.
+ * @param storage Storage to update when there is an update with the task list.
+ */
+ @Override
+ public String execute(TaskList tasks, Ui ui, Storage storage) throws DukeException {
+ Todo todo = new Todo(description);
+ tasks.add(todo);
+ storage.save(tasks.getAllTasks());
+ return Ui.showAddMessage(todo, tasks.size());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/command/Command.java b/src/main/java/duke/command/Command.java
new file mode 100644
index 0000000000..503b97589c
--- /dev/null
+++ b/src/main/java/duke/command/Command.java
@@ -0,0 +1,32 @@
+package duke.command;
+
+import duke.DukeException;
+import duke.Storage;
+import duke.task.TaskList;
+import duke.Ui;
+
+/**
+ * Executes the current command.
+ */
+public class Command {
+
+ /**
+ * Execute the current command.
+ *
+ * @param tasks Task list.
+ * @param ui UI of the application to interact with users.
+ * @param storage Storage to update when there is an update with the task list.
+ */
+ public String execute(TaskList tasks, Ui ui, Storage storage) throws DukeException {
+ return null;
+ }
+
+ /**
+ * Determines whether the program should be terminated.
+ *
+ * @return Boolean value if true then exit, if false then not exit.
+ */
+ public boolean isExit() {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/command/DeleteCommand.java b/src/main/java/duke/command/DeleteCommand.java
new file mode 100644
index 0000000000..a1053bd2bb
--- /dev/null
+++ b/src/main/java/duke/command/DeleteCommand.java
@@ -0,0 +1,47 @@
+package duke.command;
+
+import duke.Ui;
+import duke.Storage;
+import duke.DukeException;
+import duke.task.Task;
+import duke.task.TaskList;
+
+/**
+ * Deletes task from task list.
+ */
+public class DeleteCommand extends Command {
+ private int taskNum;
+
+ /**
+ * Main constructor.
+ *
+ * @param taskNum number of the task to be deleted.
+ */
+ public DeleteCommand(int taskNum) {
+ this.taskNum = taskNum;
+ }
+
+ /**
+ * Deletes task from the task list.
+ * Asks UI to print the output.
+ *
+ * @param tasks Task list.
+ * @param ui UI of the application to interact with users.
+ * @param storage Storage to update when there is an update with the task list.
+ */
+ @Override
+ public String execute(TaskList tasks, Ui ui, Storage storage) throws DukeException {
+ boolean isSmallerThanOne = taskNum < 1;
+ boolean isGreaterThanSize = taskNum > tasks.size() + 1;
+ if (isSmallerThanOne || isGreaterThanSize) {
+ throw new DukeException("\u2639 OOPS!!! The index to mark as done cannot be less than 0 or "
+ + "greater than the length of the list.");
+ }
+
+ Task deletedTask = tasks.get(taskNum - 1);
+ tasks.delete(taskNum - 1);
+ assert (tasks.size() == taskNum - 1) : "Tasks size should be equal to previous tasks size - 1";
+ storage.save(tasks.getAllTasks());
+ return Ui.showDeleteMessage(deletedTask, tasks.size());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/command/ExitCommand.java b/src/main/java/duke/command/ExitCommand.java
new file mode 100644
index 0000000000..a5c6895beb
--- /dev/null
+++ b/src/main/java/duke/command/ExitCommand.java
@@ -0,0 +1,34 @@
+package duke.command;
+
+import duke.Storage;
+import duke.task.TaskList;
+import duke.Ui;
+
+/**
+ * Terminates the current session.
+ */
+public class ExitCommand extends Command {
+
+ /**
+ * Terminates the current session.
+ * Ask UI to print output.
+ *
+ * @param tasks Task list.
+ * @param ui UI of the application to interact with users.
+ * @param storage Storage to update when there is an update with the task list.
+ */
+ @Override
+ public String execute(TaskList tasks, Ui ui, Storage storage) {
+ return Ui.showExit();
+ }
+
+ /**
+ * Determines whether the program should be terminated.
+ *
+ * @return Boolean value if true then exit, if false then not exit.
+ */
+ @Override
+ public boolean isExit() {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/command/FindCommand.java b/src/main/java/duke/command/FindCommand.java
new file mode 100644
index 0000000000..b1c4ac962b
--- /dev/null
+++ b/src/main/java/duke/command/FindCommand.java
@@ -0,0 +1,35 @@
+package duke.command;
+
+import duke.Storage;
+import duke.task.TaskList;
+import duke.Ui;
+
+/**
+ * Finds the task with a certain keyword.
+ */
+public class FindCommand extends Command {
+ private String userInput;
+
+ /**
+ * Main constructor
+ *
+ * @param userInput keyword of the task.
+ */
+ public FindCommand(String userInput) {
+ this.userInput = userInput;
+ }
+
+ /**
+ * Finds the task with a certain keyword.
+ * Asks UI to list the tasks that contain the keyword.
+ *
+ * @param tasks Task list.
+ * @param ui UI of the application to interact with users.
+ * @param storage Storage to update when there is an update with the task list.
+ */
+ @Override
+ public String execute(TaskList tasks, Ui ui, Storage storage) {
+ TaskList filteredTasks = tasks.find(userInput);
+ return Ui.showFind(filteredTasks.getAllTasks());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/command/ListCommand.java b/src/main/java/duke/command/ListCommand.java
new file mode 100644
index 0000000000..a33e431da3
--- /dev/null
+++ b/src/main/java/duke/command/ListCommand.java
@@ -0,0 +1,22 @@
+package duke.command;
+
+import duke.Storage;
+import duke.task.TaskList;
+import duke.Ui;
+
+/**
+ * Shows list of tasks.
+ */
+public class ListCommand extends Command {
+ /**
+ * Asks UI to list the tasks.
+ *
+ * @param tasks Task list.
+ * @param ui UI of the application to interact with users.
+ * @param storage Storage to update when there is an update with the task list.
+ */
+ @Override
+ public String execute(TaskList tasks, Ui ui, Storage storage) {
+ return Ui.showList(tasks.getAllTasks());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/command/MarkCommand.java b/src/main/java/duke/command/MarkCommand.java
new file mode 100644
index 0000000000..24debfbbd7
--- /dev/null
+++ b/src/main/java/duke/command/MarkCommand.java
@@ -0,0 +1,44 @@
+package duke.command;
+
+import duke.Ui;
+import duke.Storage;
+import duke.DukeException;
+import duke.task.TaskList;
+
+/**
+ * Marks task as done.
+ */
+public class MarkCommand extends Command {
+ private int taskNum;
+
+ /**
+ * Main constructor.
+ *
+ * @param taskNum number of the task to be marked.
+ */
+ public MarkCommand(int taskNum) {
+ this.taskNum = taskNum;
+ }
+
+ /**
+ * Marks the task as done in the task list.
+ * Asks UI to print the marked task.
+ *
+ * @param tasks Task list.
+ * @param ui UI of the application to interact with users.
+ * @param storage Storage to update when there is an update with the task list.
+ */
+ @Override
+ public String execute(TaskList tasks, Ui ui, Storage storage) throws DukeException {
+ boolean isSmallerThanZero = taskNum < 0;
+ boolean isGreaterThanSize = taskNum > tasks.size() + 1;
+ if (isSmallerThanZero || isGreaterThanSize) {
+ throw new DukeException("\u2639 OOPS!!! The index to mark as done cannot be less than 0 or "
+ + "greater than the length of the list.");
+ }
+
+ tasks.markAsDone(taskNum);
+ storage.save(tasks.getAllTasks());
+ return Ui.showMessage("Nice! I've marked this task as done:\n\t" + tasks.get(taskNum));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/command/UnmarkCommand.java b/src/main/java/duke/command/UnmarkCommand.java
new file mode 100644
index 0000000000..b716839e70
--- /dev/null
+++ b/src/main/java/duke/command/UnmarkCommand.java
@@ -0,0 +1,44 @@
+package duke.command;
+
+import duke.Ui;
+import duke.Storage;
+import duke.DukeException;
+import duke.task.TaskList;
+
+/**
+ * Marks task as undone.
+ */
+public class UnmarkCommand extends Command {
+ private int taskNum;
+
+ /**
+ * Main constructor.
+ *
+ * @param taskNum number of the task to be marked.
+ */
+ public UnmarkCommand(int taskNum) {
+ this.taskNum = taskNum;
+ }
+
+ /**
+ * Marks the task as done in the task list.
+ * Asks UI to print the marked task.
+ *
+ * @param tasks Task list.
+ * @param ui UI of the application to interact with users.
+ * @param storage Storage to update when there is an update with the task list.
+ */
+ @Override
+ public String execute(TaskList tasks, Ui ui, Storage storage) throws DukeException {
+ boolean isSmallerThanZero = taskNum < 0;
+ boolean isGreaterThanSize = taskNum > tasks.size() + 1;
+ if (isSmallerThanZero || isGreaterThanSize) {
+ throw new DukeException("\u2639 OOPS!!! The index to mark as done cannot be less than 0 or "
+ + "greater than the length of the list.");
+ }
+
+ tasks.markAsUndone(taskNum);
+ storage.save(tasks.getAllTasks());
+ return Ui.showMessage("OK, I've marked this task as not done yet:\n\t" + tasks.get(taskNum));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/task/Deadline.java b/src/main/java/duke/task/Deadline.java
new file mode 100644
index 0000000000..a91d9051ac
--- /dev/null
+++ b/src/main/java/duke/task/Deadline.java
@@ -0,0 +1,35 @@
+package duke.task;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class Deadline extends Task {
+ private String deadlineTime;
+ private Date dueDate;
+
+ public Deadline(String description, String deadlineTime) {
+ super(description);
+ this.deadlineTime = deadlineTime;
+ }
+
+ public Deadline(String description, Date dueDate) {
+ super(description);
+ this.dueDate = dueDate;
+ }
+
+ @Override
+ public String getOutputFormat() {
+ return String.format("D | %d | %s | %s", isDone ? 1 : 0, description, deadlineTime);
+ }
+
+ @Override
+ public String toString() {
+ if (dueDate != null) {
+ DateFormat dateDueFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
+ return String.format("[D]%s (by: %s)", super.toString(), dateDueFormat.format(dueDate));
+ } else {
+ return String.format("[D]%s (by: %s)", super.toString(), deadlineTime);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/task/Event.java b/src/main/java/duke/task/Event.java
new file mode 100644
index 0000000000..8b2d9ec466
--- /dev/null
+++ b/src/main/java/duke/task/Event.java
@@ -0,0 +1,35 @@
+package duke.task;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class Event extends Task {
+ private String eventTime;
+ private Date eventDate;
+
+ public Event(String description, String eventTime) {
+ super(description);
+ this.eventTime = eventTime;
+ }
+
+ public Event(String description, Date eventDate) {
+ super(description);
+ this.eventDate = eventDate;
+ }
+
+ @Override
+ public String getOutputFormat() {
+ return String.format("E | %d | %s | %s", isDone ? 1 : 0, description, eventTime);
+ }
+
+ @Override
+ public String toString() {
+ if (eventDate != null) {
+ DateFormat dateEventFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
+ return String.format("[E]%s (at: %s)", super.toString(), dateEventFormat.format(eventDate));
+ } else {
+ return String.format("[E]%s (at: %s)", super.toString(), eventTime);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/task/Task.java b/src/main/java/duke/task/Task.java
new file mode 100644
index 0000000000..79f3129559
--- /dev/null
+++ b/src/main/java/duke/task/Task.java
@@ -0,0 +1,36 @@
+package duke.task;
+
+public class Task {
+ protected String description;
+ protected boolean isDone;
+
+ public Task(String description) {
+ this.description = description;
+ this.isDone = false;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ public String getStatusIcon() {
+ return (isDone ? "X" : " "); // mark done task with X
+ }
+
+ public void markAsDone(){
+ this.isDone = true;
+ }
+
+ public void markAsUndone(){
+ this.isDone = false;
+ }
+
+ public String getOutputFormat() {
+ return String.format("X | %d | %s", isDone ? 1 : 0, description);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("[%s] %s", this.getStatusIcon(), this.description);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/task/TaskList.java b/src/main/java/duke/task/TaskList.java
new file mode 100644
index 0000000000..f1e4127e90
--- /dev/null
+++ b/src/main/java/duke/task/TaskList.java
@@ -0,0 +1,57 @@
+package duke.task;
+
+import java.util.ArrayList;
+public class TaskList {
+ ArrayList tasks;
+
+ public TaskList() {
+ this.tasks = new ArrayList();
+ }
+ public TaskList(ArrayList tasks) {
+ this.tasks = tasks;
+ }
+
+ public ArrayList getAllTasks() {
+ return tasks;
+ }
+
+ public Task markAsDone(int taskNum) {
+ this.tasks.get(taskNum).markAsDone();
+ return this.tasks.get(taskNum);
+ }
+
+ public Task markAsUndone(int taskNum) {
+ this.tasks.get(taskNum).markAsUndone();
+ return this.tasks.get(taskNum);
+ }
+
+ public Task delete(int taskNum) {
+ Task deletedTask = tasks.get(taskNum);
+ tasks.remove(taskNum);
+ return deletedTask;
+ }
+
+ public Task add(Task task) {
+ this.tasks.add(task);
+ return task;
+ }
+
+ public Task get(int taskNum) {
+ return this.tasks.get(taskNum);
+ }
+
+ public int size() {
+ return this.tasks.size();
+ }
+
+ public TaskList find(String userInput) {
+ TaskList filteredTasks = new TaskList();
+ for (int i = 0; i < tasks.size(); i++) {
+ String currentTask = this.tasks.get(i).getDescription();
+ if (currentTask.contains(userInput)) {
+ filteredTasks.add(this.tasks.get(i));
+ }
+ }
+ return filteredTasks;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/task/Todo.java b/src/main/java/duke/task/Todo.java
new file mode 100644
index 0000000000..7ebece3a15
--- /dev/null
+++ b/src/main/java/duke/task/Todo.java
@@ -0,0 +1,17 @@
+package duke.task;
+
+public class Todo extends Task {
+ public Todo(String description) {
+ super(description);
+ }
+
+ @Override
+ public String getOutputFormat() {
+ return String.format("T | %d | %s", isDone ? 1 : 0, description);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("[T]%s", super.toString());
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png
new file mode 100644
index 0000000000..d893658717
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..3c82f45461
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..e3730d1587
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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..832f84382e
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/java/duke/task/DeadlineTest.java b/src/test/java/duke/task/DeadlineTest.java
new file mode 100644
index 0000000000..4ca3aa7634
--- /dev/null
+++ b/src/test/java/duke/task/DeadlineTest.java
@@ -0,0 +1,19 @@
+package duke.task;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class DeadlineTest {
+ @Test
+ public void output() {
+ Deadline deadline = new Deadline("return book", "Sunday");
+ assertEquals("D | 0 | return book | Sunday", deadline.getOutputFormat());
+ }
+
+ @Test
+ public void string() {
+ Deadline deadline = new Deadline("return book", "Sunday");
+ assertEquals("[D][ ] return book (by: Sunday)", deadline.toString());
+ }
+}
diff --git a/src/test/java/duke/task/TodoTest.java b/src/test/java/duke/task/TodoTest.java
new file mode 100644
index 0000000000..f61076e41d
--- /dev/null
+++ b/src/test/java/duke/task/TodoTest.java
@@ -0,0 +1,19 @@
+package duke.task;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class TodoTest {
+ @Test
+ public void output() {
+ Todo todo = new Todo("borrow book");
+ assertEquals("T | 0 | borrow book", todo.getOutputFormat());
+ }
+
+ @Test
+ public void string() {
+ Todo todo = new Todo("borrow book");
+ assertEquals("[T][ ] borrow book", todo.toString());
+ }
+}
\ No newline at end of file
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 657e74f6e7..4d832a368d 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,7 +1,2 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
-
+Welcome! I'm duke.Duke.
+What can I do for you?
\ No newline at end of file
diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat
index 0873744649..62752b8814 100644
--- a/text-ui-test/runtest.bat
+++ b/text-ui-test/runtest.bat
@@ -15,7 +15,7 @@ IF ERRORLEVEL 1 (
REM no error here, errorlevel == 0
REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT
-java -classpath ..\bin Duke < input.txt > ACTUAL.TXT
+java -classpath ..\bin duke.Duke < input.txt > ACTUAL.TXT
REM compare the output to the expected output
FC ACTUAL.TXT EXPECTED.TXT
diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh
old mode 100644
new mode 100755