diff --git a/.gitignore b/.gitignore
index 2873e189e1..05a3d5ce36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,7 @@ bin/
/text-ui-test/ACTUAL.TXT
text-ui-test/EXPECTED-UNIX.TXT
+*.class
+duke.txt
+tasks.txt
+data.txt
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000..bb420086c2
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,5 @@
+{
+ "java.configuration.updateBuildConfiguration": "interactive",
+ "java.checkstyle.configuration": "${workspaceFolder}/config/checkstyle/checkstyle.xml",
+ "java.checkstyle.version": "9.3"
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..70f0691218
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,62 @@
+plugins {
+ id 'java'
+ id 'application'
+ id 'com.github.johnrengelman.shadow' version '7.1.2'
+ id 'checkstyle'
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.10.0'
+ testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0'
+
+ String javaFxVersion = '17.0.7'
+
+ 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 {
+ mainClass.set("xavier/Launcher")
+}
+
+shadowJar {
+ archiveBaseName = "xavier"
+ archiveClassifier = null
+ dependsOn("distZip", "distTar")
+}
+
+run {
+ standardInput = System.in
+}
+
+checkstyle {
+ toolVersion = '10.2'
+}
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000000..eb761a9b9a
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,434 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
new file mode 100644
index 0000000000..39efb6e4ac
--- /dev/null
+++ b/config/checkstyle/suppressions.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/docs/README.md b/docs/README.md
index 8077118ebe..487f251ac3 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,29 +1,180 @@
-# User Guide
+# Xavier User Guide
+## Introduction
+
+![Ui](./Ui.png)
+
+Xavier is a simple chatbot which helps to manage your daily tasks!
+
+Download it from [here](https://github.com/shunjieee/ip/releases/download/A-Release/xavier.jar) now!
+
+---
## Features
+List of features supported:
+* Add
+* Delete
+* List
+* Mark
+* Unmark
+* Find
+* Sort
+* Exit
+
+---
+### Adding a task: `todo`, `deadline` and `event`
+
+There are three types tasks that Xavier supports, to-do, deadline and event. The commands add the respective task to the task list.
+
+***todo***
+
+Formart:
+`todo `
+
+Example:
+`todo wash dishes`
+
+Expected outcome:
+```
+Got it. I've added this task:
+ [T][ ] wash dishes
+Now you have 1 tasks in the list.
+```
+
+***deadline***
+
+Format:
+`deadline /by `
+
+Example:
+`deadline CS2103T Quiz 7 /by 7/3/2024-2359`
+
+Expected outcome:
+```
+Got it. I've added this task:
+ [D][ ] CS2103T Quiz 7 (by: Mar 07 2024, 23:59)
+Now you have 2 tasks in the list.
+```
+
+***event***
+
+Format:
+`event /from /to `
+
+Example:
+`event Taylor Swift Concert /from 3/3/2024-1900 /to 3/3/2024-2200`
+
+Expected outcome:
+```
+Got it. I've added this task:
+ [E][ ] Taylor Swift Concert (from: Mar 03 2024, 19:00 to: Mar 03 2024, 22:00)
+Now you have 3 tasks in the list.
+```
+
+> [!IMPORTANT]
+> The date-time format **MUST** be in **dd/MM/yyyy-HHmm**.
+
+---
+### Deleting a task: `delete`
-### Feature-ABC
+Format:
+`delete `
-Description of the feature.
+Example:
+`delete 1`
-### Feature-XYZ
+Expected outcome:
+```
+Noted, I've removed this task:
+ [T][ ] wash dishes
+Now you have 2 tasks in the list.
+```
-Description of the feature.
+---
+### Listing all the task in the list: `list`
-## Usage
+Format:
+`list`
-### `Keyword` - Describe action
+Example and expected outcome:
+```
+Here are the tasks in your list:
+1. [D][ ] CS2103T Quiz 7 (by: Mar 07 2024, 23:59)
+2. [E][ ] Taylor Swift Concert (from: Mar 03 2024, 19:00 to: Mar 03 2024, 22:00)
+```
-Describe the action and its outcome.
+---
+### Marking task as done: `mark`
-Example of usage:
+Format:
+`mark `
-`keyword (optional arguments)`
+Example:
+`mark 1`
Expected outcome:
+```
+Nice! I've marked this task as done:
+ [D][X] CS2103T Quiz 7 (by: Mar 07 2024, 23:59)
+```
+
+---
+### Unmarking task: `unmark`
+
+Format:
+`unmark `
+
+Example:
+`mark 2`
+
+Expected outcome:
+```
+OK, I've marked this task as not done yet:
+ [E][ ] Taylor Swift Concert (from: Mar 03 2024, 19:00 to: Mar 03 2024, 22:00)
+```
+
+---
+### Finding tasks in the list: `find`
+
+Format:
+`find `
+
+Example:
+`find Quiz`
+
+Expected outcome:
+```
+Here are the matching tasks in your list:
+1. [D][X] CS2103T Quiz 7 (by: Mar 07 2024, 23:59)
+```
+> [!NOTE]
+> `keyword` is case sensitive.
+
+---
+### Sorting the list: `sort`
+
+The list will be sorting with the unmark tasks at the top and marked tasks at the bottom. In each section, the tasks will be sorted alphabetically.
+
+Format:
+`sort`
+
+Example and expected outcome:
+```
+Here are the tasks in your list:
+1. [E][ ] Taylor Swift Concert (from: Mar 03 2024, 19:00 to: Mar 03 2024, 22:00)
+2. [D][X] CS2103T Quiz 7 (by: Mar 07 2024, 23:59)
+```
+
+---
+### Exiting the program: `bye`
+
+The program will save the list in `./data/data.txt` before exiting.
-Description of the outcome.
+Format:
+`bye`
+Example and expected outcome **(only CLI)**:
```
-expected output
+Saving data ...
+Data saved successfully. :)
+Bye. Hope to see you again soon!
```
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..3403c771e6
Binary files /dev/null and b/docs/Ui.png differ
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/command/AddCommand.java b/src/main/java/command/AddCommand.java
new file mode 100644
index 0000000000..15a64aa641
--- /dev/null
+++ b/src/main/java/command/AddCommand.java
@@ -0,0 +1,145 @@
+package command;
+
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+
+import common.DukeException;
+import task.Deadline;
+import task.Event;
+import task.Task;
+import task.TaskList;
+import task.ToDo;
+
+/**
+ * {@inheritDocs}
+ * Adds a task into a tasklist.
+ */
+public class AddCommand extends Command {
+ private String command;
+ private TaskList taskList;
+ private StringTokenizer st;
+
+ /**
+ * Creates an instance of AddCommand.
+ */
+ public AddCommand(String command, TaskList taskList, StringTokenizer st) {
+ this.command = command;
+ this.taskList = taskList;
+ this.st = st;
+ }
+
+ /**
+ * {@inheritDocs}
+ * Adds a task into a tasklist.
+ *
+ * @throws DukeException If the command cannot be executed.
+ */
+ @Override
+ public String execute() throws DukeException {
+ try {
+ String taskName = "";
+
+ switch (command) {
+ case "todo":
+ while (st.hasMoreTokens()) {
+ taskName += st.nextToken() + " ";
+ }
+
+ if (taskName.equals("")) {
+ throw new DukeException("Missing task name. :(");
+
+ } else {
+ ToDo td = new ToDo(taskName.strip());
+ taskList.add(td);
+ assert taskList.contains(td) : taskName + " is not added.";
+
+ return produceResponse(td);
+ }
+
+ case "deadline":
+ String deadline = "";
+
+ while (st.hasMoreTokens()) {
+ String temp = st.nextToken();
+ if (temp.equals("/by")) {
+ deadline = st.nextToken();
+ break;
+
+ } else {
+ taskName += temp + " ";
+ }
+ }
+
+ if (taskName.equals("") || deadline.equals("")) {
+ throw new DukeException("Missing field(s) / incorrect input(s). :("
+ + "\nCheck if you have used the keyword \"/by\"");
+
+ } else {
+ Deadline d = new Deadline(taskName.strip(), deadline);
+ taskList.add(d);
+ assert taskList.contains(d) : taskName + " is not added.";
+
+ return produceResponse(d);
+ }
+
+ case "event":
+ String startTime = "";
+ String endTime = "";
+
+ while (st.hasMoreTokens()) {
+ String temp = st.nextToken();
+
+ if (temp.equals("/from")) {
+ startTime = st.nextToken();
+ continue;
+
+ } else if (temp.equals("/to")) {
+ endTime = st.nextToken();
+ break;
+
+ } else {
+ taskName += temp + " ";
+ }
+ }
+
+ if (taskName.equals("") || startTime.equals("") || endTime.equals("")) {
+ throw new DukeException("Missing field(s) / incorrect input(s). :(\n"
+ + "Check if you have used the keyword \"/from\" and \"/to\"");
+
+ } else {
+ Event e = new Event(taskName.strip(), startTime, endTime);
+ taskList.add(e);
+ assert taskList.contains(e) : taskName + " is not added.";
+
+ return produceResponse(e);
+ }
+
+ default:
+ return "Missing field(s) / incorrect input(s). :(";
+ }
+
+ } catch (NoSuchElementException e) {
+ System.out.println("Missing field(s) / incorrect input(s). :(");
+ return "Missing field(s) / incorrect input(s). :(";
+ }
+ }
+
+ private String produceResponse(Task t) {
+ String s = "Got it. I've added this task:\n"
+ + " " + t.toString()
+ + "\nNow you have " + taskList.size() + " tasks in the list.";
+
+ System.out.println(s);
+ return s;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return True if program will exit.
+ */
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/command/Command.java b/src/main/java/command/Command.java
new file mode 100644
index 0000000000..30b07e970f
--- /dev/null
+++ b/src/main/java/command/Command.java
@@ -0,0 +1,22 @@
+package command;
+
+import common.DukeException;
+
+/**
+ * Represents a command to be executed by the program.
+ */
+public abstract class Command {
+ /**
+ * Executes the command.
+ *
+ * @throws DukeException If command cannot be executed.
+ */
+ public abstract String execute() throws DukeException;
+
+ /**
+ * Returns true if the command executed exits the program.
+ *
+ * @return True if program will exit.
+ */
+ public abstract boolean isExit();
+}
diff --git a/src/main/java/command/DeleteCommand.java b/src/main/java/command/DeleteCommand.java
new file mode 100644
index 0000000000..64a5a82d00
--- /dev/null
+++ b/src/main/java/command/DeleteCommand.java
@@ -0,0 +1,52 @@
+package command;
+
+import common.DukeException;
+import task.Task;
+import task.TaskList;
+
+/**
+ * {@inheritDocs}
+ * Deletes a task from a tasklist.
+ */
+public class DeleteCommand extends Command {
+ private TaskList taskList;
+ private int taskIndex;
+
+ /**
+ * Creates an instance of DeleteCommand.
+ */
+ public DeleteCommand(TaskList taskList, int taskIndex) {
+ this.taskList = taskList;
+ this.taskIndex = taskIndex;
+ }
+
+ /**
+ * {@inheritDocs}
+ * Deletes a task from a tasklist.
+ *
+ * @throws DukeException If the command cannot be executed.
+ */
+ @Override
+ public String execute() {
+ Task t = taskList.remove(taskIndex);
+ assert !taskList.contains(t) : t.getTaskName() + " is not removed.";
+
+
+ String s = "Noted, I've removed this task:\n"
+ + " " + t.toString()
+ + "\nNow you have " + taskList.size() + " tasks in the list.";
+
+ System.out.println(s);
+ return s;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return True if program will exit.
+ */
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/command/ExitCommand.java b/src/main/java/command/ExitCommand.java
new file mode 100644
index 0000000000..bc6a455677
--- /dev/null
+++ b/src/main/java/command/ExitCommand.java
@@ -0,0 +1,29 @@
+package command;
+
+/**
+ * {@inheritDocs}
+ * Exits the program.
+ */
+public class ExitCommand extends Command {
+
+ /**
+ * {@inheritDocs}
+ * Exits the program.
+ */
+ @Override
+ public String execute() {
+ String exitMessage = "Bye. Hope to see you again soon!";
+ System.out.println(exitMessage);
+ return exitMessage;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return True if program will exit.
+ */
+ @Override
+ public boolean isExit() {
+ return true;
+ }
+}
diff --git a/src/main/java/command/FindCommand.java b/src/main/java/command/FindCommand.java
new file mode 100644
index 0000000000..bd4c4a903c
--- /dev/null
+++ b/src/main/java/command/FindCommand.java
@@ -0,0 +1,58 @@
+package command;
+
+import java.util.LinkedList;
+import java.util.StringTokenizer;
+
+import task.Task;
+import task.TaskList;
+
+/**
+ * {@inheritDocs}
+ * Finds a task by searching for a keyword.
+ */
+public class FindCommand extends Command {
+ private TaskList taskList;
+ private String keyword;
+
+ /**
+ * Creates an instance of FindCommand.
+ */
+ public FindCommand(TaskList taskList, StringTokenizer st) {
+ this.taskList = taskList;
+ keyword = st.nextToken();
+ }
+
+ /**
+ * {@inheritDocs}
+ * Finds a task by searching for a keyword.
+ */
+ @Override
+ public String execute() {
+ String result = "Here are the matching tasks in your list:\n";
+ System.out.print(result);
+
+ LinkedList tl = taskList.getList();
+
+ int counter = 1;
+ for (Task t : tl) {
+ if (t.hasKeyword(keyword)) {
+ String taskString = counter + ". " + t.toString();
+ System.out.println(taskString);
+ result += taskString + "\n";
+
+ counter++;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return True if program will exit.
+ */
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/command/ListCommand.java b/src/main/java/command/ListCommand.java
new file mode 100644
index 0000000000..f2a645b51a
--- /dev/null
+++ b/src/main/java/command/ListCommand.java
@@ -0,0 +1,49 @@
+package command;
+
+import task.Task;
+import task.TaskList;
+
+/**
+ * {@inheritDocs}
+ * List all the tasks in the task list.
+ */
+public class ListCommand extends Command {
+ private TaskList taskList;
+
+ /**
+ * Creates an instance of ListCommand.
+ */
+ public ListCommand(TaskList taskList) {
+ this.taskList = taskList;
+ }
+
+ /**
+ * {@inheritDocs}
+ * List all the tasks in the task list.
+ */
+ @Override
+ public String execute() {
+ String result = "Here are the tasks in your list:\n";
+ System.out.print(result);
+
+ for (int i = 1; i <= taskList.size(); i++) {
+ Task t = taskList.get(i);
+
+ String taskString = i + ". " + t.toString();
+ System.out.println(taskString);
+ result += taskString + "\n";
+ }
+
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return True if program will exit.
+ */
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/command/MarkCommand.java b/src/main/java/command/MarkCommand.java
new file mode 100644
index 0000000000..6a0f23a135
--- /dev/null
+++ b/src/main/java/command/MarkCommand.java
@@ -0,0 +1,49 @@
+package command;
+
+import task.Task;
+import task.TaskList;
+
+/**
+ * {@inheritDocs}
+ * Marks a task as done.
+ */
+public class MarkCommand extends Command {
+ private TaskList taskList;
+ private int taskIndex;
+
+ /**
+ * Creates an instance of MarkCommand.
+ */
+ public MarkCommand(TaskList taskList, int taskIndex) {
+ this.taskList = taskList;
+ this.taskIndex = taskIndex;
+ }
+
+ /**
+ * {@inheritDocs}
+ * Marks a task as done.
+ */
+ @Override
+ public String execute() {
+ Task t = taskList.get(taskIndex);
+ t.markAsDone();
+ assert t.checkStatus() : t.getTaskName() + " not marked as done.";
+
+ String s = "Nice! I've marked this task as done:\n"
+ + " " + t.toString();
+
+ System.out.println(s);
+
+ return s;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return True if program will exit.
+ */
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/command/SortCommand.java b/src/main/java/command/SortCommand.java
new file mode 100644
index 0000000000..92011fd082
--- /dev/null
+++ b/src/main/java/command/SortCommand.java
@@ -0,0 +1,58 @@
+package command;
+
+import java.util.Comparator;
+
+import task.Task;
+import task.TaskList;
+
+/**
+ * {@inheritDocs}
+ * Sort the tasks in the task list.
+ */
+public class SortCommand extends Command {
+ private TaskList taskList;
+
+ /**
+ * Creates an instance of SortCommand.
+ */
+ public SortCommand(TaskList taskList) {
+ this.taskList = taskList;
+ }
+
+ /**
+ * {@inheritDocs}
+ * List all the tasks in the task list.
+ */
+ @Override
+ public String execute() {
+ taskList.getList().sort(new StatusComparator());
+ String message = "I have sorted the task list according to the tasks' statuses.";
+ return message + "\n\n" + new ListCommand(taskList).execute();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return True if program will exit.
+ */
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+
+ static class StatusComparator implements Comparator {
+ // undone status are on top of the list
+ @Override
+ public int compare(Task task1, Task task2) {
+ if (!task1.checkStatus() && task2.checkStatus()) {
+ return -1;
+
+ } else if (task1.checkStatus() && !task2.checkStatus()) {
+ return 1;
+
+ } else {
+ return task1.getTaskName().compareToIgnoreCase(task2.getTaskName());
+ }
+ }
+ }
+}
diff --git a/src/main/java/command/UnmarkCommand.java b/src/main/java/command/UnmarkCommand.java
new file mode 100644
index 0000000000..33f37899ed
--- /dev/null
+++ b/src/main/java/command/UnmarkCommand.java
@@ -0,0 +1,49 @@
+package command;
+
+import task.Task;
+import task.TaskList;
+
+/**
+ * {@inheritDocs}
+ * Marks a task as undone.
+ */
+public class UnmarkCommand extends Command {
+ private TaskList taskList;
+ private int taskIndex;
+
+ /**
+ * Creates an instance of UnmarkCommand.
+ */
+ public UnmarkCommand(TaskList taskList, int taskIndex) {
+ this.taskList = taskList;
+ this.taskIndex = taskIndex;
+ }
+
+ /**
+ * {@inheritDocs}
+ * Marks a task as undone.
+ */
+ @Override
+ public String execute() {
+ Task t = taskList.get(taskIndex);
+ t.unmark();
+ assert !t.checkStatus() : t.getTaskName() + " not unmarked.";
+
+ String s = "OK, I've marked this task as not done yet:\n"
+ + " " + t.toString();
+
+ System.out.println(s);
+
+ return s;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return True if program will exit.
+ */
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/common/DukeException.java b/src/main/java/common/DukeException.java
new file mode 100644
index 0000000000..bee2efb9f6
--- /dev/null
+++ b/src/main/java/common/DukeException.java
@@ -0,0 +1,15 @@
+package common;
+
+/**
+ * Represents an exception specific to this program.
+ */
+public class DukeException extends Exception {
+ /**
+ * Creates an exception with the provided error message.
+ *
+ * @param message The error message of the exception.
+ */
+ public DukeException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/common/Parser.java b/src/main/java/common/Parser.java
new file mode 100644
index 0000000000..85bb004631
--- /dev/null
+++ b/src/main/java/common/Parser.java
@@ -0,0 +1,93 @@
+package common;
+
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+
+import command.AddCommand;
+import command.Command;
+import command.DeleteCommand;
+import command.ExitCommand;
+import command.FindCommand;
+import command.ListCommand;
+import command.MarkCommand;
+import command.SortCommand;
+import command.UnmarkCommand;
+import task.TaskList;
+
+/**
+ * Parses the user input and creates a command accordingly.
+ */
+public class Parser {
+ private StringTokenizer st;
+ private String command;
+ private TaskList tasks;
+
+ /**
+ * Creates an instance of a Parser object to parse the user input.
+ *
+ * @param fullCommand The entire user input.
+ * @param tasks The list of tasks.
+ */
+ public Parser(String fullCommand, TaskList tasks) {
+ st = new StringTokenizer(fullCommand);
+ command = st.nextToken().toLowerCase();
+ this.tasks = tasks;
+ }
+
+ /**
+ * Returns the command object after parsing through the user input.
+ *
+ * @return The command object created.
+ * @throws IndexOutOfBoundException If the user input a number more/less than the number of tasks in the list.
+ * @throws NumberFormatException If the user input is invalid
+ * @throws NoSuchElementException If the user input is invalid
+ * @throws DukeException If there are other unexpected errors
+ */
+ public Command parse() throws IndexOutOfBoundsException, NumberFormatException,
+ NoSuchElementException, DukeException {
+
+ Command cmd;
+ switch (command) {
+ case "list":
+ cmd = new ListCommand(tasks);
+ return cmd;
+
+ case "mark":
+ int indexOfTaskToMark = Integer.parseInt(st.nextToken());
+ cmd = new MarkCommand(tasks, indexOfTaskToMark);
+ return cmd;
+
+ case "unmark":
+ int indexOfTaskToUnmark = Integer.parseInt(st.nextToken());
+ cmd = new UnmarkCommand(tasks, indexOfTaskToUnmark);
+ return cmd;
+
+ case "delete":
+ int indexOfTaskToDelete = Integer.parseInt(st.nextToken());
+ cmd = new DeleteCommand(tasks, indexOfTaskToDelete);
+ return cmd;
+
+ case "todo":
+ case "deadline":
+ case "event":
+ cmd = new AddCommand(command, tasks, st);
+ return cmd;
+
+ case "find":
+ cmd = new FindCommand(tasks, st);
+ return cmd;
+
+ case "sort":
+ cmd = new SortCommand(tasks);
+ return cmd;
+
+ case "bye":
+ cmd = new ExitCommand();
+ return cmd;
+
+ default:
+ throw new DukeException("OOPS!! Pls try again, hustler. :)");
+ }
+ }
+}
+
diff --git a/src/main/java/common/Storage.java b/src/main/java/common/Storage.java
new file mode 100644
index 0000000000..c0b08bca09
--- /dev/null
+++ b/src/main/java/common/Storage.java
@@ -0,0 +1,160 @@
+package common;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.Scanner;
+
+import task.Deadline;
+import task.Event;
+import task.Task;
+import task.TaskList;
+import task.ToDo;
+
+/**
+ * Loads and saves the list of tasks to a file.
+ */
+public class Storage {
+ private static int MAX_ATTEMPT = 2;
+ private String filepath;
+
+ /**
+ * Returns an instance of Storage that will load/save to the given filepath.
+ *
+ * @param filepath The filepath of the file to load/saves the tasks.
+ */
+ public Storage(String filepath) {
+ this.filepath = filepath;
+ }
+
+ /**
+ * Checks if filepath exist, if not creates one from the current directory.
+ * Maximum attempt of 2, else the program will exit with error.
+ */
+ public void checkForFilePath() {
+ int currentAttempt = 0;
+ File file;
+
+ while (++currentAttempt <= MAX_ATTEMPT) {
+ try {
+ Ui.showLine();
+ String counter = "Startup Attempt #" + currentAttempt + "/" + MAX_ATTEMPT + ":";
+ System.out.println(counter);
+
+ file = new File(filepath);
+ if (file.createNewFile()) {
+ String s1 = "tasks.txt does not exist.";
+ String s2 = "tasks.txt successfully created.";
+ System.out.println(s1);
+ System.out.println(s2);
+
+ } else {
+ String s = "tasks.txt already exist.";
+ System.out.println(s);
+ }
+ break;
+
+ } catch (IOException e) {
+ System.out.println("IOException occured: " + e.getMessage());
+
+ File dir = new File("./data");
+ boolean isDirectoryCreated = dir.mkdir();
+ if (isDirectoryCreated) {
+ String s = "Directory ./data created.";
+ System.out.println(s);
+ }
+
+ } finally {
+ Ui.showLine();
+ }
+ }
+ }
+
+ /**
+ * Returns the list of tasks loaded from the storage file.
+ *
+ * @return The list of tasks loaded in a LinkedList.
+ */
+ public LinkedList loadData() {
+ checkForFilePath();
+ LinkedList tasks = new LinkedList<>();
+
+ try {
+ Scanner fileScanner = new Scanner(new File(filepath));
+ boolean hasError = false;
+
+ while (fileScanner.hasNext()) {
+ String[] taskFields = fileScanner.nextLine().split(" \\| ");
+ String taskType = taskFields[0];
+ boolean isDone = Integer.parseInt(taskFields[1]) == 1 ? true : false;
+
+ if (taskType.equals("T")) {
+ ToDo td = new ToDo(isDone, taskFields[2]);
+ tasks.add(td);
+
+ } else if (taskType.equals("D")) {
+ try {
+ Deadline d = new Deadline(isDone, taskFields[2], taskFields[3]);
+ tasks.add(d);
+
+ } catch (DukeException e) {
+ System.out.println(e.getMessage() + "\n");
+ hasError = true;
+ }
+
+ } else if (taskType.equals("E")) {
+ try {
+ Event e = new Event(isDone, taskFields[2], taskFields[3]);
+ tasks.add(e);
+
+ } catch (DukeException e) {
+ System.out.println(e.getMessage() + "\n");
+ hasError = true;
+ }
+ }
+ }
+ fileScanner.close();
+
+ if (hasError) {
+ Ui.showLine();
+
+ } else {
+ String s = "tasks.txt loaded without error.";
+ System.out.println(s);
+ Ui.showLine();
+ }
+
+ } catch (FileNotFoundException e) {
+ System.out.println("FileNotFoundException occured: " + e.getMessage());
+ }
+ return tasks;
+ }
+
+ /**
+ * Saves the current list of tasks to the storage file.
+ *
+ * @param tasks The list of tasks to save.
+ */
+ public void saveDataAndExit(TaskList tasks) {
+ try {
+ System.out.println("Saving data ...");
+
+ File file = new File(filepath);
+ FileWriter fw = new FileWriter(file, false);
+
+ LinkedList taskList = tasks.getList();
+ for (Task t : taskList) {
+ fw.write(t.toData());
+ fw.write(System.lineSeparator());
+ }
+
+ fw.close();
+ System.out.println("Data saved successfully. :)");
+
+ } catch (IOException e) {
+ System.out.println("Error while saving data: " + e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/common/Ui.java b/src/main/java/common/Ui.java
new file mode 100644
index 0000000000..f5ea7ef084
--- /dev/null
+++ b/src/main/java/common/Ui.java
@@ -0,0 +1,37 @@
+package common;
+
+import java.util.Scanner;
+
+/**
+ * Represents the user interface that interacts with the user.
+ */
+public class Ui {
+ private Scanner scanner = new Scanner(System.in);
+
+ /**
+ * Reads the user input.
+ */
+ public String readCommand() {
+ return scanner.nextLine().strip();
+ }
+
+ /**
+ * Shows the start up message upon successful loading of the program.
+ */
+ public String showWelcome() {
+ String welcomeMessage = "Good Morning, Hustler! I'm Xavier.\n"
+ + "What can I do for you?";
+ System.out.println(welcomeMessage);
+ showLine();
+
+ return welcomeMessage;
+ }
+
+ /**
+ * Shows the separating line for different messages.
+ */
+ public static void showLine() {
+ String line = "________________________________________\n";
+ System.out.println(line);
+ }
+}
diff --git a/src/main/java/task/Deadline.java b/src/main/java/task/Deadline.java
new file mode 100644
index 0000000000..b6fed99be1
--- /dev/null
+++ b/src/main/java/task/Deadline.java
@@ -0,0 +1,66 @@
+package task;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+import common.DukeException;
+
+/**
+ * {@inheritDoc}
+ */
+public class Deadline extends Task {
+ protected LocalDateTime deadline;
+ private DateTimeFormatter receivingFormatter = DateTimeFormatter.ofPattern("d/M/yyyy-HHmm");
+ private DateTimeFormatter printingFormatter = DateTimeFormatter.ofPattern("MMM dd yyyy, HH:mm");
+
+ /**
+ * {@inheritDoc}
+ */
+ public Deadline(String taskName, String deadline) throws DukeException {
+ super(taskName);
+
+ try {
+ this.deadline = LocalDateTime.parse(deadline, receivingFormatter);
+
+ } catch (DateTimeParseException e) {
+ throw new DukeException("Date and Time not in the correct format.\n"
+ + "Correct format: dd/MM/yyyy-HHmm\n"
+ + "Received: " + deadline + "\n"
+ + "\"" + taskName + "\" not added to the list.");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Deadline(boolean isDone, String taskName, String deadline) throws DukeException {
+ super(isDone, taskName);
+
+ try {
+ this.deadline = LocalDateTime.parse(deadline, printingFormatter);
+
+ } catch (DateTimeParseException e) {
+ throw new DukeException("Date and Time not in the correct format.\n"
+ + "Correct format: MMM dd yyyy, HH:mm\n"
+ + "Received: " + taskName + " | " + deadline + "\n"
+ + "\"" + taskName + "\" removed from the list.");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return "[D]" + super.toString() + " (by: " + deadline.format(printingFormatter) + ")";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toData() {
+ return "D | " + super.toData() + " | " + deadline.format(printingFormatter);
+ }
+}
diff --git a/src/main/java/task/Event.java b/src/main/java/task/Event.java
new file mode 100644
index 0000000000..70fe6c4894
--- /dev/null
+++ b/src/main/java/task/Event.java
@@ -0,0 +1,81 @@
+package task;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+import common.DukeException;
+
+/**
+ * {@inheritDoc}
+ */
+public class Event extends Task {
+ protected LocalDateTime startTime;
+ protected LocalDateTime endTime;
+ private DateTimeFormatter receivingFormatter = DateTimeFormatter.ofPattern("d/M/yyyy-HHmm");
+ private DateTimeFormatter printingFormatter = DateTimeFormatter.ofPattern("MMM dd yyyy, HH:mm");
+
+ /**
+ * {@inheritDoc}
+ */
+ public Event(String taskName, String startTime, String endTime) throws DukeException {
+ super(taskName);
+
+ try {
+ this.startTime = LocalDateTime.parse(startTime, receivingFormatter);
+
+ } catch (DateTimeParseException e) {
+ throw new DukeException("Start time not in the correct format.\n"
+ + "Correct format: dd/MM/yyyy-HHmm\n"
+ + "Received: " + startTime + "\n"
+ + "\"" + taskName + "\" not added to the list.");
+ }
+
+ try {
+ this.endTime = LocalDateTime.parse(endTime, receivingFormatter);
+
+ } catch (DateTimeParseException e) {
+ throw new DukeException("End time not in the correct format.\n"
+ + "Correct format: dd/MM/yyyy-HHmm\n"
+ + "Received: " + endTime + "\n"
+ + "\"" + taskName + "\" not added to the list.");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Event(boolean isDone, String taskName, String time) throws DukeException {
+ super(isDone, taskName);
+
+ try {
+ String[] startToEndTime = time.split(" - ");
+ startTime = LocalDateTime.parse(startToEndTime[0], printingFormatter);
+ endTime = LocalDateTime.parse(startToEndTime[1], printingFormatter);
+
+ } catch (DateTimeParseException e) {
+ throw new DukeException("Date and Time not in the correct format.\n"
+ + "Correct format: MMM dd yyyy, HH:mm\n"
+ + "Received: " + taskName + " | " + time + "\n"
+ + "\"" + taskName + "\" removed from the list.");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return "[E]" + super.toString() + " (from: " + startTime.format(printingFormatter)
+ + " to: " + endTime.format(printingFormatter) + ")";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toData() {
+ return "E | " + super.toData() + " | " + startTime.format(printingFormatter)
+ + " - " + endTime.format(printingFormatter);
+ }
+}
diff --git a/src/main/java/task/Task.java b/src/main/java/task/Task.java
new file mode 100644
index 0000000000..63040eb5d1
--- /dev/null
+++ b/src/main/java/task/Task.java
@@ -0,0 +1,89 @@
+package task;
+
+/**
+ * Represents a task to be stored in the list of tasks.
+ */
+public class Task {
+ protected String taskName;
+ protected boolean isDone;
+
+ /**
+ * Returns an instance of Task. Mark by default as undone.
+ *
+ * @param taskName User-defined task name.
+ */
+ public Task(String taskName) {
+ this.taskName = taskName;
+ this.isDone = false;
+ }
+
+ /**
+ * Returns an instance of Task. Status of task is provided by the user.
+ *
+ * @param isDone The status of the task.
+ * @param taskName User-defined task name.
+ */
+ public Task(boolean isDone, String taskName) {
+ this.taskName = taskName;
+ this.isDone = isDone;
+ }
+
+ /**
+ * Returns task name
+ */
+ public String getTaskName() {
+ return taskName;
+ }
+
+ /**
+ * Returns task status
+ *
+ * @return True, if done. Else, false.
+ */
+ public boolean checkStatus() {
+ return isDone;
+ }
+
+ /**
+ * Returns the status of the task.
+ *
+ * @return A string to indicate the status.
+ */
+ public String getStatusIcon() {
+ return (isDone ? "[X] " : "[ ] "); // mark done task with X
+ }
+
+ /**
+ * Sets the status of task as done.
+ */
+ public void markAsDone() {
+ this.isDone = true;
+ }
+
+ /**
+ * Sets the status of task as not done.
+ */
+ public void unmark() {
+ this.isDone = false;
+ }
+
+ /**
+ * Returns the string format of the task for printing to the ui.
+ */
+ @Override
+ public String toString() {
+ return getStatusIcon() + taskName;
+ }
+
+ /**
+ * Returns the string format of the task for writing to save file.
+ */
+ public String toData() {
+ return (isDone ? "1" : "0") + " | " + taskName;
+ }
+
+ public boolean hasKeyword(String keyword) {
+ return taskName.contains(keyword);
+ }
+}
+
diff --git a/src/main/java/task/TaskList.java b/src/main/java/task/TaskList.java
new file mode 100644
index 0000000000..7df864fef4
--- /dev/null
+++ b/src/main/java/task/TaskList.java
@@ -0,0 +1,62 @@
+package task;
+
+import java.util.LinkedList;
+
+/**
+ * Represents a list of tasks to be stored.
+ */
+public class TaskList {
+ private LinkedList tasks;
+
+ /**
+ * Creates an instance of TaskList containing tasks.
+ *
+ * @param tasks List of tasks to be stored upon instantiating.
+ */
+ public TaskList(LinkedList tasks) {
+ this.tasks = tasks;
+ }
+
+ /**
+ * Returns the number of tasks in the list.
+ */
+ public int size() {
+ return tasks.size();
+ }
+
+ /**
+ * Get a task from the list. User-input is 1-indexed.
+ */
+ public Task get(int i) {
+ return tasks.get(i - 1);
+ }
+
+ /**
+ * Adds the task into the list.
+ */
+ public void add(Task task) {
+ tasks.add(task);
+ }
+
+ /**
+ * Removes a task from the list. User-input is 1-indexed.
+ */
+ public Task remove(int indexOfTask) {
+ Task task = tasks.remove(indexOfTask - 1);
+ return task;
+ }
+
+ public LinkedList getList() {
+ return tasks;
+ }
+
+ /**
+ * Checks if task is in the list.
+ *
+ * @param t The task to be checked.
+ * @return True if task is in the list. Else, false.
+ */
+ public boolean contains(Task t) {
+ return tasks.contains(t);
+ }
+}
diff --git a/src/main/java/task/ToDo.java b/src/main/java/task/ToDo.java
new file mode 100644
index 0000000000..7ffaf32d14
--- /dev/null
+++ b/src/main/java/task/ToDo.java
@@ -0,0 +1,37 @@
+package task;
+
+/**
+ * {@inheritDoc}
+ */
+public class ToDo extends Task {
+
+ /**
+ * {@inheritDoc}
+ */
+ public ToDo(String taskName) {
+ super(taskName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ToDo(boolean isDone, String taskName) {
+ super(isDone, taskName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return "[T]" + super.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toData() {
+ return "T | " + super.toData();
+ }
+}
diff --git a/src/main/java/xavier/DialogBox.java b/src/main/java/xavier/DialogBox.java
new file mode 100644
index 0000000000..0f83e86672
--- /dev/null
+++ b/src/main/java/xavier/DialogBox.java
@@ -0,0 +1,61 @@
+package xavier;
+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 getXavierDialog(String text, Image img) {
+ var db = new DialogBox(text, img);
+ db.flip();
+ return db;
+ }
+}
diff --git a/src/main/java/xavier/Launcher.java b/src/main/java/xavier/Launcher.java
new file mode 100644
index 0000000000..6eecd88e76
--- /dev/null
+++ b/src/main/java/xavier/Launcher.java
@@ -0,0 +1,11 @@
+package xavier;
+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/xavier/Main.java b/src/main/java/xavier/Main.java
new file mode 100644
index 0000000000..5e95fe22e8
--- /dev/null
+++ b/src/main/java/xavier/Main.java
@@ -0,0 +1,52 @@
+package xavier;
+import java.io.IOException;
+
+import javafx.application.Application;
+import javafx.application.Platform;
+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 Xavier xavier = new Xavier("./data/data.txt");
+
+ @Override
+ public void start(Stage stage) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml"));
+ AnchorPane ap = fxmlLoader.load();
+ Scene scene = new Scene(ap);
+
+ stage.setScene(scene);
+ stage.setTitle("I'm Xavier, serving your's truly :)");
+ fxmlLoader.getController().setXavier(xavier);
+ fxmlLoader.getController().showWelcome();
+ stage.show();
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Exits the program when the byeCommand is executed.
+ *
+ * @param isExit Boolean generated after each command is executed.
+ */
+ public static void exit(boolean isExit) {
+ if (isExit) {
+ try {
+ Thread.sleep(1500);
+ Platform.exit();
+
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/src/main/java/xavier/MainWindow.java b/src/main/java/xavier/MainWindow.java
new file mode 100644
index 0000000000..81ebd4b225
--- /dev/null
+++ b/src/main/java/xavier/MainWindow.java
@@ -0,0 +1,62 @@
+package xavier;
+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;
+/**
+ * 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 Xavier xavier;
+
+ private Image userImage = new Image(this.getClass().getResourceAsStream("/images/DaUser.png"));
+ private Image xavierImage = new Image(this.getClass().getResourceAsStream("/images/DaXavier.png"));
+
+ @FXML
+ public void initialize() {
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+ }
+
+ public void setXavier(Xavier x) {
+ xavier = x;
+ }
+
+ /**
+ * Creates a dialog box with the welcome message and appends it to the dialog container.
+ */
+ @FXML
+ public void showWelcome() {
+ dialogContainer.getChildren().add(
+ DialogBox.getXavierDialog(xavier.showWelcome(), xavierImage)
+ );
+ }
+
+ /**
+ * Creates two dialog boxes, one echoing user input and the other containing xavier's reply and then appends them to
+ * the dialog container. Clears the user input after processing.
+ */
+ @FXML
+ private void handleUserInput() {
+ String input = userInput.getText();
+ String response = xavier.getResponse(input);
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialog(input, userImage),
+ DialogBox.getXavierDialog(response, xavierImage)
+ );
+ userInput.clear();
+ Platform.runLater(() -> Main.exit(xavier.getIsExit()));
+ }
+}
diff --git a/src/main/java/xavier/Xavier.java b/src/main/java/xavier/Xavier.java
new file mode 100644
index 0000000000..efdb6e0926
--- /dev/null
+++ b/src/main/java/xavier/Xavier.java
@@ -0,0 +1,76 @@
+package xavier;
+import java.util.NoSuchElementException;
+
+import command.Command;
+import common.DukeException;
+import common.Parser;
+import common.Storage;
+import common.Ui;
+import task.TaskList;
+
+/**
+ * The Duke program implements a chatbot, now named NextGenerationJarvis, that keeps track of tasks for the user.
+ */
+public class Xavier {
+ private Storage storage;
+ private TaskList tasks;
+ private Ui ui;
+ private boolean isExit;
+
+ /**
+ * Returns an instance of the program and loads the tasks from the file found at the provided filepath.
+ *
+ * @param filepath The filepath of the file to load/save the tasks.
+ */
+ public Xavier(String filepath) {
+ ui = new Ui();
+ storage = new Storage(filepath);
+ tasks = new TaskList(storage.loadData());
+
+ }
+
+ public String showWelcome() {
+ return ui.showWelcome();
+ }
+
+ /**
+ * Returns the response from the program as a String when given the input provided.
+ *
+ * @param input The input for the program to parse and execute.
+ * @return The response from the program.
+ */
+ public String getResponse(String input) {
+ try {
+ Command cmd = new Parser(input, tasks).parse();
+ isExit = cmd.isExit();
+ if (isExit) {
+ storage.saveDataAndExit(tasks);
+ return cmd.execute();
+ }
+ return cmd.execute();
+
+ } catch (IndexOutOfBoundsException e) {
+ System.out.println("Invalid task number. :(");
+ return "Invalid task number. :(";
+
+ } catch (NumberFormatException e) {
+ System.out.println("Input is not an integer. :(");
+ return "Input is not an integer. :(";
+
+ } catch (NoSuchElementException e) {
+ System.out.println("Missing task number. :(");
+ return "Missing task number. :(";
+
+ } catch (DukeException e) {
+ System.out.println(e.getMessage());
+ return e.getMessage();
+
+ } finally {
+ Ui.showLine();
+ }
+ }
+
+ public boolean getIsExit() {
+ return isExit;
+ }
+}
diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png
new file mode 100644
index 0000000000..4872387890
Binary files /dev/null and b/src/main/resources/images/DaUser.png differ
diff --git a/src/main/resources/images/DaXavier.png b/src/main/resources/images/DaXavier.png
new file mode 100644
index 0000000000..45a16e8725
Binary files /dev/null and b/src/main/resources/images/DaXavier.png differ
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 0000000000..bfd5f702f8
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
new file mode 100644
index 0000000000..d9e881aaa7
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/task/DeadlineTest.java b/src/test/java/task/DeadlineTest.java
new file mode 100644
index 0000000000..519a2e7b47
--- /dev/null
+++ b/src/test/java/task/DeadlineTest.java
@@ -0,0 +1,51 @@
+package task; //same package as the class being tested
+
+import org.junit.jupiter.api.Test;
+
+import common.DukeException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class DeadlineTest {
+ @Test
+ public void testDeadlineCreatedSuccessfully() {
+ try {
+ new Deadline("taskName", "12/10/2010-1900");
+ } catch (Exception e) {
+ System.out.println("Deadline not created");
+ }
+ }
+
+ @Test
+ public void testWrongDataTimeFormat() {
+ try {
+ new Deadline("taskName", "20-2-2022, 1200");
+ } catch (DukeException e) {
+ String errorMessage = ("Date and Time not in the correct format.\n"
+ + "Correct format: dd/MM/yyyy-HHmm\n"
+ + "Received: " + "20-2-2022, 1200" + "\n"
+ + "\"" + "taskName" + "\" not added to the list.");
+
+ assertEquals(errorMessage, e.getMessage());
+ }
+ }
+
+ @Test
+ public void testToStringSuccessful() {
+ try {
+ Deadline d = new Deadline("taskName", "10/10/2012-1900");
+ assertEquals("[D][ ] taskName (by: Oct 10 2012, 19:00)", d.toString());
+ } catch (DukeException e) {
+ }
+ }
+
+ @Test
+ public void testToDataSuccessful() {
+ try {
+ Deadline d = new Deadline("taskName", "10/10/2012-1900");
+ assertEquals("D | 0 | taskName | Oct 10 2012, 19:00", d.toData());
+ } catch (DukeException e) {
+ }
+
+ }
+}
diff --git a/src/test/java/task/EventTest.java b/src/test/java/task/EventTest.java
new file mode 100644
index 0000000000..abd269ebc6
--- /dev/null
+++ b/src/test/java/task/EventTest.java
@@ -0,0 +1,65 @@
+package task; //same package as the class being tested
+
+import org.junit.jupiter.api.Test;
+
+import common.DukeException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class EventTest {
+ @Test
+ public void testEventCreatedSuccessfully() {
+ try {
+ new Event("taskName", "12/10/2010-1900", "12/10/2010-2000");
+ } catch (Exception e) {
+ System.out.println("Deadline not created");
+ }
+ }
+
+ @Test
+ public void testWrongStartDataTimeFormat() {
+ try {
+ new Event("taskName", "20-2-2022, 1200", "12/10/2022-2200");
+ } catch (DukeException e) {
+ String errorMessage = ("Start time not in the correct format.\n"
+ + "Correct format: dd/MM/yyyy-HHmm\n"
+ + "Received: " + "20-2-2022, 1200" + "\n"
+ + "\"" + "taskName" + "\" not added to the list.");
+
+ assertEquals(errorMessage, e.getMessage());
+ }
+ }
+
+ @Test
+ public void testWrongEndDataTimeFormat() {
+ try {
+ new Event("taskName", "20/2/2022-1200", "12/10/2022");
+ } catch (DukeException e) {
+ String errorMessage = ("End time not in the correct format.\n"
+ + "Correct format: dd/MM/yyyy-HHmm\n"
+ + "Received: " + "12/10/2022" + "\n"
+ + "\"" + "taskName" + "\" not added to the list.");
+
+ assertEquals(errorMessage, e.getMessage());
+ }
+ }
+
+ @Test
+ public void testToStringSuccessful() {
+ try {
+ Event e = new Event("taskName", "10/10/2012-1900", "12/10/2022-2200");
+ assertEquals("[E][ ] taskName (from: Oct 10 2012, 19:00 to: Oct 12 2022, 22:00)", e.toString());
+ } catch (DukeException e) {
+ }
+ }
+
+ @Test
+ public void testToDataSuccessful() {
+ try {
+ Event e = new Event("taskName", "10/10/2012-1900", "12/10/2022-2200");
+ assertEquals("E | 0 | taskName | Oct 10 2012, 19:00 - Oct 12 2022, 22:00", e.toData());
+ } catch (DukeException e) {
+ }
+
+ }
+}
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 657e74f6e7..162a8dc149 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -5,3 +5,66 @@ Hello from
| |_| | |_| | < __/
|____/ \__,_|_|\_\___|
+________________________________________
+Hello! I'm NextGenerationJarvis.
+What can I do for you?
+________________________________________
+
+
+________________________________________
+Got it. I've added this task:
+ [T][ ] borrow book
+Now you have 1 tasks in the list.
+________________________________________
+
+
+________________________________________
+Here are the tasks in your list:
+1. [T][ ] borrow book
+________________________________________
+
+
+________________________________________
+Got it. I've added this task:
+ [D][ ] return book (by: Sunday)
+Now you have 2 tasks in the list.
+________________________________________
+
+
+________________________________________
+Got it. I've added this task:
+ [E][ ] project meeting (from: Mon 2pm to: 4pm)
+Now you have 3 tasks in the list.
+________________________________________
+
+
+________________________________________
+Here are the tasks in your list:
+1. [T][ ] borrow book
+2. [D][ ] return book (by: Sunday)
+3. [E][ ] project meeting (from: Mon 2pm to: 4pm)
+________________________________________
+
+
+________________________________________
+Nice! I've marked this task as done:
+ [T][X] borrow book
+________________________________________
+
+
+________________________________________
+Nice! I've marked this task as done:
+ [D][X] return book (by: Sunday)
+________________________________________
+
+
+________________________________________
+OK, I've marked this task as not done yet:
+ [T][ ] borrow book
+________________________________________
+
+
+________________________________________
+Bye. Hope to see you again soon!
+________________________________________
+
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index e69de29bb2..09168e32ce 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -0,0 +1,9 @@
+todo borrow book
+list
+deadline return book /by Sunday
+event project meeting /from Mon 2pm /to 4pm
+list
+mark 1
+mark 2
+unmark 1
+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