diff --git a/.gitignore b/.gitignore
index 2873e189e1..4da6229490 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,5 @@ bin/
/text-ui-test/ACTUAL.TXT
text-ui-test/EXPECTED-UNIX.TXT
+Johan.class
+test_johan.txt
diff --git a/README.md b/README.md
index af0309a9ef..be006b04f1 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Duke project template
+# johan.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 17, update Intellij to the most recent version.
1. If there are any further prompts, accept the defaults.
1. Configure the project to use **JDK 17** (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.
-1. 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:
+1. After that, locate the `src/main/java/johan.Duke.java` file, right-click it, and choose `Run johan.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..17ddf9f00b
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,77 @@
+plugins {
+ id 'java'
+ id 'application'
+ id 'checkstyle'
+ id 'com.github.johnrengelman.shadow' version '7.1.2'
+}
+
+repositories {
+ mavenCentral()
+ maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
+}
+
+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'
+ implementation group: 'com.joestelmach', name: 'natty', version: '0.13'
+ checkstyle('com.puppycrawl.tools:checkstyle:10.12.7') {
+ exclude group: 'com.google.collections', module: 'google-collections'
+ }
+ 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("johan.launcher.Launcher")
+}
+
+shadowJar {
+ archiveBaseName = "johan"
+ archiveClassifier = null
+ archiveFileName = 'johan.jar'
+ mergeServiceFiles()
+ archiveVersion.set(new Date().format('yyyyMMdd-HHmmss'))
+ archiveFileName = "johan-${archiveVersion.get()}.jar"
+}
+
+run {
+ standardInput = System.in
+ enableAssertions = true
+}
+
+test {
+ useJUnitPlatform()
+}
+
+checkstyle {
+ toolVersion = '10.2'
+ configFile = file('config/checkstyle/checkstyle.xml')
+ ignoreFailures = true
+ showViolations = true
+}
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000000..a1f4a94ff0
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,434 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
new file mode 100644
index 0000000000..135ea49ee0
--- /dev/null
+++ b/config/checkstyle/suppressions.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/data/johan.txt b/data/johan.txt
new file mode 100644
index 0000000000..e3e2482db7
--- /dev/null
+++ b/data/johan.txt
@@ -0,0 +1,4 @@
+T | 0 | read book
+E | 0 | project meeting | 12/2/2019 | 12/3/2020
+D | 0 | submit | 1/3/2025
+D | 0 | review | 1/2/2025
diff --git a/docs/README.md b/docs/README.md
index 47b9f984f7..7fd227fb8b 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,30 +1,84 @@
-# Duke User Guide
+
+# Johan Chatbot User Guide
-// Update the title above to match the actual product name
+
-// Product screenshot goes here
+Johan Chatbot is a JavaFX-based task manager that lets you organize your todos, deadlines, and events through a simple chat interface. Type commands in the text field and hit "Send" to manage your tasks efficiently.
-// Product intro goes here
+## Quick Start
-## Adding deadlines
+1. Download `johan.jar` from [releases](https://github.com/jhwan0707/ip/releases) or build it with `./gradlew shadowJar`.
+2. **Run**: `java -jar build/libs/johan.jar`.
-// Describe the action and its outcome.
+## Adding Deadlines
-// Give examples of usage
+Add a task with a due date to keep track of time-sensitive duties.
-Example: `keyword (optional arguments)`
+- **Command**: `deadline /by `
+- **Examples**:
+ - `deadline submit report /by 2025-03-01`
+ - `deadline pay bills /by 01/03/2025`
+- **Outcome**: Adds a deadline to your list, displayed as `[D][ ] (by: Mar 1 2025)`.
-// A description of the expected outcome goes here
+## Managing Tasks
-```
-expected output
-```
+### Listing Tasks
+View all your tasks in the current order.
-## Feature ABC
+- **Command**: `list`
+- **Example**: `list`
+- **Outcome**: Shows all tasks with numbers (e.g., `1. [D][ ] submit report (by: Mar 1 2025)`).
-// Feature details
+### Marking Tasks
+Mark tasks as done or undone.
+- **Command**: `mark ` or `unmark `
+- **Examples**: `mark 1`, `unmark 1`
+- **Outcome**: Updates task status (e.g., `[D][X] submit report` or `[D][ ] submit report`).
-## Feature XYZ
+### Deleting Tasks
+Remove a task from your list.
-// Feature details
\ No newline at end of file
+- **Command**: `delete `
+- **Example**: `delete 1`
+- **Outcome**: Deletes the task at position 1.
+
+## Sorting Tasks
+
+Sort deadlines chronologically and other tasks alphabetically.
+
+- **Command**: `sort`
+- **Example**: `sort`
+ - Before: `1. [D][ ] submit /by 2025-03-01`, `2. [D][ ] review /by 2025-02-01`
+ - After: `1. [D][ ] review /by 2025-02-01`, `2. [D][ ] submit /by 2025-03-01`
+- **Outcome**: Reorders tasks (e.g., deadlines by date, todos alphabetically).
+
+## Searching Tasks
+
+### Finding Tasks
+Search tasks by keyword.
+
+- **Command**: `find `
+- **Example**: `find report`
+- **Outcome**: Lists matching tasks (e.g., `1. [D][ ] submit report (by: Mar 1 2025)`).
+
+### Tasks on a Date
+View tasks due or occurring on a specific date.
+
+- **Command**: `on `
+- **Example**: `on 2025-03-01`
+- **Outcome**: Shows relevant tasks (e.g., deadlines due on March 1, 2025).
+
+## Exiting
+
+Close the chatbot and save your tasks.
+
+- **Command**: `bye`
+- **Example**: `bye`
+- **Outcome**: Exits the app, saving tasks to `data/johan.txt`.
+
+## Notes
+
+- Task numbers are 1-based.
+- Dates can be `YYYY-MM-DD` or `DD/MM/YYYY`.
+- Commands are case-insensitive.
\ No newline at end of file
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..62d05832b8
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..033e24c4cd
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..66c01cfeba
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000000..fcb6fca147
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ 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
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# 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"'
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000000..6689b85bee
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,92 @@
+@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=.
+@rem This is normally unused
+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% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 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!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/src/main/java/data/johan.txt b/src/main/java/data/johan.txt
new file mode 100644
index 0000000000..6ebc238016
--- /dev/null
+++ b/src/main/java/data/johan.txt
@@ -0,0 +1,3 @@
+D | 1 | readbook | 2/12/2019
+E | 0 | try clothes | 10/2/2019 | 1/3/2020
+T | 0 | read book
diff --git a/src/main/java/johan/DialogBox.java b/src/main/java/johan/DialogBox.java
new file mode 100644
index 0000000000..4c31bd21d1
--- /dev/null
+++ b/src/main/java/johan/DialogBox.java
@@ -0,0 +1,78 @@
+package johan;
+
+
+import java.io.IOException;
+import java.util.Collections;
+
+import javafx.collections.FXCollections;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.geometry.Pos;
+import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.HBox;
+
+/**
+ * Represents a dialog box with text and an image in the Johan GUI.
+ */
+public class DialogBox extends HBox {
+ @FXML
+ private Label dialog;
+ @FXML
+ private ImageView displayPicture;
+
+ /**
+ * Constructs a DialogBox with the specified text and image using FXML.
+ *
+ * @param text The text to display
+ * @param image The image to display
+ */
+ private DialogBox(String text, Image image) {
+ 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(image);
+ }
+
+ /**
+ * Flips the dialog box so the image is on the left and text on the right.
+ */
+ private void flip() {
+ this.setAlignment(Pos.TOP_LEFT);
+ var children = FXCollections.observableArrayList(this.getChildren());
+ Collections.reverse(children); // Use Collections.reverse()
+ this.getChildren().setAll(children);
+ }
+
+ /**
+ * Creates a dialog box for a user message (image on right).
+ *
+ * @param text The user's message text
+ * @param image The user's avatar image
+ * @return A new DialogBox instance
+ */
+ public static DialogBox getUserDialog(String text, Image image) {
+ return new DialogBox(text, image);
+ }
+
+ /**
+ * Creates a dialog box for a Johan message (image on left).
+ *
+ * @param text Johan's message text
+ * @param image Johan's avatar image
+ * @return A new DialogBox instance
+ */
+ public static DialogBox getJohanDialog(String text, Image image) {
+ DialogBox db = new DialogBox(text, image);
+ db.flip();
+ return db;
+ }
+}
diff --git a/src/main/java/Duke.java b/src/main/java/johan/Duke.java
similarity index 86%
rename from src/main/java/Duke.java
rename to src/main/java/johan/Duke.java
index 5d313334cc..8177679984 100644
--- a/src/main/java/Duke.java
+++ b/src/main/java/johan/Duke.java
@@ -1,3 +1,8 @@
+package johan;
+
+/**
+ * Main class for Duke Program
+ */
public class Duke {
public static void main(String[] args) {
String logo = " ____ _ \n"
diff --git a/src/main/java/johan/Johan.java b/src/main/java/johan/Johan.java
new file mode 100644
index 0000000000..79feffebf3
--- /dev/null
+++ b/src/main/java/johan/Johan.java
@@ -0,0 +1,126 @@
+package johan;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+import johan.command.Command;
+import johan.parser.Parser;
+import johan.storage.Storage;
+import johan.task.Task;
+import johan.task.TaskList;
+import johan.ui.Ui;
+
+
+// to run
+// from repos dir >> javac -d bin src/main/java/johan/*.java src/main/java/johan/*/*.java
+// java -cp bin johan.Johan
+
+/**
+ * Main class for the Johan task management application.
+ */
+public class Johan {
+ private final Storage storage;
+ private final TaskList tasks;
+ private final Ui ui;
+ private final Parser parser;
+
+ /**
+ * Constructs a Johan instance with the specified storage file path.
+ *
+ * @param filePath The path to the storage file
+ */
+ public Johan(String filePath) {
+ this.ui = new Ui();
+ this.storage = new Storage(filePath);
+ this.parser = new Parser();
+ this.tasks = loadTasks();
+ }
+ /**
+ * Constructs a Johan instance with explicit dependencies (GUI mode).
+ * @param storage The storage instance
+ * @param tasks The task list instance
+ * @param parser The parser instance
+ */
+ public Johan(Storage storage, TaskList tasks, Parser parser) {
+ assert storage != null : "Storage should not be null";
+ assert tasks != null : "Task list should not be null";
+ assert parser != null : "Parser should not be null";
+ this.storage = storage;
+ this.tasks = tasks;
+ this.parser = parser;
+ this.ui = null;
+ }
+ private TaskList loadTasks() {
+ ArrayList loadedTasks;
+ try {
+ loadedTasks = storage.loadTasks();
+ assert loadedTasks != null : "Task list should not be null";
+ } catch (Exception e) {
+ ui.showError("Failed to load tasks: " + e.getMessage());
+ loadedTasks = new ArrayList<>();
+ }
+ return new TaskList(loadedTasks);
+ }
+ /**
+ * Runs the main application loop, processing user commands until exit.
+ */
+ public void run() {
+ ui.showWelcome();
+ boolean isExit = false;
+ while (!isExit) {
+ try {
+ String fullCommand = ui.readCommand();
+ ui.showLine();
+ Command command = parser.parse(fullCommand);
+ command.execute(tasks, ui, storage);
+ isExit = command.isExit();
+ } catch (Exception e) {
+ ui.showError(e.getMessage());
+ } finally {
+ ui.showLine();
+ }
+ }
+ ui.showGoodbye();
+ }
+
+ /**
+ * Executes a user command and sends output to the provided consumer (GUI mode).
+ * @param input The user command string
+ * @param outputConsumer A consumer to handle output messages
+ * @throws Exception If the command execution fails
+ */
+ public void executeCommand(String input, Consumer outputConsumer) throws Exception {
+ assert input != null && !input.isEmpty() : "Input should not be null";
+ Ui guiUi = new Ui(outputConsumer);
+ Command command = parser.parse(input);
+ command.execute(tasks, guiUi, storage);
+ // TODO: Use streams to process tasks (e.g., filter) in future increments
+ }
+ /**
+ * Main entry point for the application.
+ *
+ * @param args Command-line arguments (unused)
+ */
+ public static void main(String[] args) {
+ new Johan("./data/johan.txt").run();
+ }
+ /**
+ * Parses a date string into a LocalDate object.
+ *
+ * @param dateStr The date string to parse
+ * @return The parsed LocalDate
+ */
+ public static LocalDate parseDate(String dateStr) {
+ try {
+ DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("d/M/yyyy")
+ .withLocale(java.util.Locale.ENGLISH);
+ return LocalDate.parse(dateStr.trim(), formatter1);
+ } catch (Exception e) {
+ DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd")
+ .withLocale(java.util.Locale.ENGLISH);
+ return LocalDate.parse(dateStr.trim(), formatter2);
+ }
+ }
+}
diff --git a/src/main/java/johan/Main.java b/src/main/java/johan/Main.java
new file mode 100644
index 0000000000..5b522d955c
--- /dev/null
+++ b/src/main/java/johan/Main.java
@@ -0,0 +1,50 @@
+package johan;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.layout.AnchorPane;
+import javafx.stage.Stage;
+import johan.parser.Parser;
+import johan.storage.Storage;
+import johan.task.TaskList;
+
+/**
+ * The main JavaFX application class for Johan, providing a GUI interface using FXML.
+ */
+public class Main extends Application {
+ private Johan johan;
+
+ @Override
+ public void start(Stage stage) {
+ try {
+ Storage storage = new Storage("./data/johan.txt");
+ Parser parser = new Parser();
+ ArrayList loadedTasks;
+ try {
+ loadedTasks = storage.loadTasks();
+ } catch (Exception e) {
+ loadedTasks = new ArrayList<>();
+ }
+ TaskList tasks = new TaskList(loadedTasks);
+ this.johan = new Johan(storage, tasks, parser);
+
+ FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml"));
+ AnchorPane ap = fxmlLoader.load();
+ Scene scene = new Scene(ap);
+ stage.setTitle("Johan Chatbot");
+ stage.setResizable(false);
+ stage.setScene(scene);
+
+ MainWindow controller = fxmlLoader.getController();
+ controller.setJohan(johan);
+
+ stage.show();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/johan/MainWindow.java b/src/main/java/johan/MainWindow.java
new file mode 100644
index 0000000000..d103c6934e
--- /dev/null
+++ b/src/main/java/johan/MainWindow.java
@@ -0,0 +1,63 @@
+package johan;
+
+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.VBox;
+
+/**
+ * Controller for the main GUI window in Johan.
+ */
+public class MainWindow {
+ @FXML
+ private ScrollPane scrollPane;
+ @FXML
+ private VBox dialogContainer;
+ @FXML
+ private TextField userInput;
+ @FXML
+ private Button sendButton;
+
+ private Johan johan;
+ private Image userImage = new Image(getClass().getResourceAsStream("/images/User.png"));
+ private Image johanImage = new Image(getClass().getResourceAsStream("/images/Johan.png"));
+
+ /**
+ * Initializes the controller after FXML is loaded.
+ */
+ @FXML
+ public void initialize() {
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+ dialogContainer.getChildren()
+ .add(DialogBox.getJohanDialog("Hello! I'm Johan. What can I do for you?", johanImage));
+ }
+
+ /**
+ * Sets the Johan instance for this controller.
+ *
+ * @param j The Johan instance to use
+ */
+ public void setJohan(Johan j) {
+ this.johan = j;
+ }
+
+ @FXML
+ private void handleUserInput() {
+ String input = userInput.getText().trim();
+ if (!input.isEmpty()) {
+ dialogContainer.getChildren().add(DialogBox.getUserDialog(input, userImage));
+ try {
+ johan.executeCommand(input, message -> dialogContainer.getChildren().add(
+ DialogBox.getJohanDialog(message, johanImage)));
+ if (input.equals("bye")) {
+ javafx.application.Platform.exit();
+ }
+ } catch (Exception e) {
+ dialogContainer.getChildren().add(DialogBox.getJohanDialog("Oops! " + e.getMessage(), johanImage));
+ }
+ userInput.clear();
+ }
+ }
+}
diff --git a/src/main/java/johan/command/AddCommand.java b/src/main/java/johan/command/AddCommand.java
new file mode 100644
index 0000000000..98b4a1ad44
--- /dev/null
+++ b/src/main/java/johan/command/AddCommand.java
@@ -0,0 +1,36 @@
+package johan.command;
+
+import johan.storage.Storage;
+import johan.task.TaskList;
+import johan.ui.Ui;
+
+/**
+ * Abstract base class for commands that add tasks to the task list.
+ */
+public abstract class AddCommand extends Command {
+ protected final String description;
+
+ /**
+ * Constructs an AddCommand with the specified task description.
+ *
+ * @param description the description of the task to be added
+ */
+ public AddCommand(String description) {
+ this.description = description;
+ }
+
+ /**
+ * Executes the command to add a task to the task list.
+ *
+ * @param tasks The task list to modify
+ * @param ui The user interface for displaying output
+ * @param storage The storage system for persisting tasks
+ * @throws Exception If an error occurs during exception
+ */
+ @Override
+ public abstract void execute(TaskList tasks, Ui ui, Storage storage) throws Exception;
+}
+
+
+
+
diff --git a/src/main/java/johan/command/Command.java b/src/main/java/johan/command/Command.java
new file mode 100644
index 0000000000..2d404ccafb
--- /dev/null
+++ b/src/main/java/johan/command/Command.java
@@ -0,0 +1,27 @@
+package johan.command;
+
+import johan.storage.Storage;
+import johan.task.TaskList;
+import johan.ui.Ui;
+
+/**
+ * Abstract base class for all commands in the application
+ */
+public abstract class Command {
+ /**
+ * Executes the command with the given task list, UI, and storage.
+ * @param tasks The task list to operate on
+ * @param ui The user interface for displaying output
+ * @param storage The storage system for persisting tasks
+ * @throws Exception If an error occurs during execution
+ */
+ public abstract void execute(TaskList tasks, Ui ui, Storage storage) throws Exception;
+
+ /**
+ * Indicates whether this command should terminate the application.
+ * @return true if this is an exit command, false otherwise
+ */
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/johan/command/DeadlineCommand.java b/src/main/java/johan/command/DeadlineCommand.java
new file mode 100644
index 0000000000..485743905c
--- /dev/null
+++ b/src/main/java/johan/command/DeadlineCommand.java
@@ -0,0 +1,38 @@
+package johan.command;
+
+import johan.storage.Storage;
+import johan.task.Deadline;
+import johan.task.Task;
+import johan.task.TaskList;
+import johan.ui.Ui;
+
+/**
+ * Command to add a deadline task to the task list.
+ */
+public class DeadlineCommand extends AddCommand {
+ private final String by;
+
+ /**
+ * Constructs a DeadlineCommand with the specified description and deadline.
+ * @param description The description of the deadline task
+ * @param by The deadline date/time string
+ */
+ public DeadlineCommand(String description, String by) {
+ super(description);
+ this.by = by;
+ }
+
+ /**
+ * Adds a deadline task to the task list and updates storage.
+ * @param tasks The task list to modify
+ * @param ui The user interface for displaying output
+ * @param storage The storage system for persisting tasks
+ */
+ @Override
+ public void execute(TaskList tasks, Ui ui, Storage storage) {
+ Task task = new Deadline(description, by);
+ tasks.addTask(task);
+ ui.showTaskAdded(task, tasks.size());
+ storage.saveTasks(tasks.getTasks());
+ }
+}
diff --git a/src/main/java/johan/command/DeleteCommand.java b/src/main/java/johan/command/DeleteCommand.java
new file mode 100644
index 0000000000..b18ab67a45
--- /dev/null
+++ b/src/main/java/johan/command/DeleteCommand.java
@@ -0,0 +1,34 @@
+package johan.command;
+
+import johan.storage.Storage;
+import johan.task.Task;
+import johan.task.TaskList;
+import johan.ui.Ui;
+
+/**
+ * Command to delete a task from the task list.
+ */
+public class DeleteCommand extends Command {
+ private final int taskIndex;
+
+ /**
+ * Constructs a DeleteCommand for the specified task index
+ * @param taskIndex The zero-based index of the task to delete
+ */
+ public DeleteCommand(int taskIndex) {
+ this.taskIndex = taskIndex;
+ }
+
+ /**
+ * Deletes the task at the specified index and updates storage.
+ * @param tasks The task list to operate on
+ * @param ui The user interface for displaying output
+ * @param storage The storage system for persisting tasks
+ */
+ @Override
+ public void execute(TaskList tasks, Ui ui, Storage storage) {
+ Task task = tasks.deleteTask(taskIndex);
+ ui.showTaskDeleted(task, tasks.size());
+ storage.saveTasks(tasks.getTasks());
+ }
+}
diff --git a/src/main/java/johan/command/EventCommand.java b/src/main/java/johan/command/EventCommand.java
new file mode 100644
index 0000000000..9af3cb5ab4
--- /dev/null
+++ b/src/main/java/johan/command/EventCommand.java
@@ -0,0 +1,41 @@
+package johan.command;
+
+import johan.storage.Storage;
+import johan.task.Event;
+import johan.task.Task;
+import johan.task.TaskList;
+import johan.ui.Ui;
+
+/**
+ * Command to add an event task to the task list.
+ */
+public class EventCommand extends AddCommand {
+ private final String from;
+ private final String to;
+
+ /**
+ * Constructs an EventCommand with the specified description and time range.
+ * @param description The description of the event
+ * @param from The start date/time string
+ * @param to The end date/time string
+ */
+ public EventCommand(String description, String from, String to) {
+ super(description);
+ this.from = from;
+ this.to = to;
+ }
+
+ /**
+ * Adds an event task to the task list and updates storage.
+ * @param tasks The task list to modify
+ * @param ui The user interface for displaying output
+ * @param storage The storage system for persisting tasks
+ */
+ @Override
+ public void execute(TaskList tasks, Ui ui, Storage storage) {
+ Task task = new Event(description, from, to);
+ tasks.addTask(task);
+ ui.showTaskAdded(task, tasks.size());
+ storage.saveTasks(tasks.getTasks());
+ }
+}
diff --git a/src/main/java/johan/command/ExitCommand.java b/src/main/java/johan/command/ExitCommand.java
new file mode 100644
index 0000000000..feac4bcc5e
--- /dev/null
+++ b/src/main/java/johan/command/ExitCommand.java
@@ -0,0 +1,30 @@
+package johan.command;
+
+import johan.storage.Storage;
+import johan.task.TaskList;
+import johan.ui.Ui;
+
+/**
+ * Command to exit the application.
+ */
+public class ExitCommand extends Command {
+ /**
+ * Performs no action as termination is handled by isExit().
+ * @param tasks The task list to operate on
+ * @param ui The user interface for displaying output
+ * @param storage The storage system for persisting tasks
+ */
+ @Override
+ public void execute(TaskList tasks, Ui ui, Storage storage) {
+ // No action needed; isExit() handles termination
+ }
+
+ /**
+ * Indicates that this command terminates the application.
+ * @return true always
+ */
+ @Override
+ public boolean isExit() {
+ return true;
+ }
+}
diff --git a/src/main/java/johan/command/FindCommand.java b/src/main/java/johan/command/FindCommand.java
new file mode 100644
index 0000000000..a942e19d61
--- /dev/null
+++ b/src/main/java/johan/command/FindCommand.java
@@ -0,0 +1,43 @@
+package johan.command;
+
+import java.util.ArrayList;
+
+import johan.storage.Storage;
+import johan.task.Task;
+import johan.task.TaskList;
+import johan.ui.Ui;
+
+/**
+ * Command to find tasks containing a specific keyword in their description.
+ */
+public class FindCommand extends Command {
+ private final String keyword;
+
+ /**
+ * Constructs a FindCommand with the specified search keyword.
+ *
+ * @param keyword The keyword to search for in task descriptions
+ */
+ public FindCommand(String keyword) {
+ this.keyword = keyword;
+ }
+
+ /**
+ * Executes the command to find and display tasks matching the keyword.
+ *
+ * @param tasks The task list to search
+ * @param ui The user interface for displaying results
+ * @param storage The storage system (unused in this command)
+ */
+ @Override
+ public void execute(TaskList tasks, Ui ui, Storage storage) {
+ ArrayList matchingTasks = new ArrayList<>();
+ for (int i = 0; i < tasks.size(); i++) {
+ Task task = tasks.getTask(i);
+ if (task.getDescription().toLowerCase().contains(this.keyword.toLowerCase())) {
+ matchingTasks.add(task);
+ }
+ }
+ ui.showFoundTasks(matchingTasks);
+ }
+}
diff --git a/src/main/java/johan/command/ListCommand.java b/src/main/java/johan/command/ListCommand.java
new file mode 100644
index 0000000000..c7dd5ca591
--- /dev/null
+++ b/src/main/java/johan/command/ListCommand.java
@@ -0,0 +1,21 @@
+package johan.command;
+
+import johan.storage.Storage;
+import johan.task.TaskList;
+import johan.ui.Ui;
+
+/**
+ * Command to list all tasks in the task list.
+ */
+public class ListCommand extends Command {
+ /**
+ * Displays all tasks in the task list.
+ * @param tasks The task list to display
+ * @param ui The user interface for displaying output
+ * @param storage The storage system for persisting tasks
+ */
+ @Override
+ public void execute(TaskList tasks, Ui ui, Storage storage) {
+ ui.showTaskList(tasks);
+ }
+}
diff --git a/src/main/java/johan/command/MarkCommand.java b/src/main/java/johan/command/MarkCommand.java
new file mode 100644
index 0000000000..6c31110aec
--- /dev/null
+++ b/src/main/java/johan/command/MarkCommand.java
@@ -0,0 +1,43 @@
+package johan.command;
+
+import johan.storage.Storage;
+import johan.task.Task;
+import johan.task.TaskList;
+import johan.ui.Ui;
+
+/**
+ * Command to mark a task as done or not done.
+ */
+public class MarkCommand extends Command {
+ private final int taskIndex;
+ private final boolean markAsDone;
+
+ /**
+ * Constructs a MarkCommand for the specified task and mark status.
+ * @param taskIndex The zero-based index of the task to mark
+ * @param markAsDone True to mark as done, false to mark as not done
+ */
+ public MarkCommand(int taskIndex, boolean markAsDone) {
+ this.taskIndex = taskIndex;
+ this.markAsDone = markAsDone;
+ }
+
+ /**
+ * Marks the specified task and updates storage.
+ * @param tasks The task list to operate on
+ * @param ui The user interface for displaying output
+ * @param storage The storage system for persisting tasks
+ */
+ @Override
+ public void execute(TaskList tasks, Ui ui, Storage storage) {
+ Task task = tasks.getTask(taskIndex);
+ if (markAsDone) {
+ task.markAsDone();
+ ui.showTaskMarked(task, true);
+ } else {
+ task.markAsNotDone();
+ ui.showTaskMarked(task, false);
+ }
+ storage.saveTasks(tasks.getTasks());
+ }
+}
diff --git a/src/main/java/johan/command/OnDateCommand.java b/src/main/java/johan/command/OnDateCommand.java
new file mode 100644
index 0000000000..a63a1ee10f
--- /dev/null
+++ b/src/main/java/johan/command/OnDateCommand.java
@@ -0,0 +1,34 @@
+package johan.command;
+
+import java.time.LocalDate;
+
+import johan.Johan;
+import johan.storage.Storage;
+import johan.task.TaskList;
+import johan.ui.Ui;
+
+/**
+ * Command to show tasks occurring on a specific date.
+ */
+public class OnDateCommand extends Command {
+ private final LocalDate targetDate;
+
+ /**
+ * Constructs an OnDateCommand for the specified date string.
+ * @param dateStr The date string to parse
+ */
+ public OnDateCommand(String dateStr) {
+ this.targetDate = Johan.parseDate(dateStr);
+ }
+
+ /**
+ * Displays all tasks occurring on the target date.
+ * @param tasks The task list to operate on
+ * @param ui The user interface for displaying output
+ * @param storage The storage system for persisting tasks
+ */
+ @Override
+ public void execute(TaskList tasks, Ui ui, Storage storage) {
+ ui.showTasksOnDate(tasks, targetDate);
+ }
+}
diff --git a/src/main/java/johan/command/SortCommand.java b/src/main/java/johan/command/SortCommand.java
new file mode 100644
index 0000000000..55a1c37ff1
--- /dev/null
+++ b/src/main/java/johan/command/SortCommand.java
@@ -0,0 +1,17 @@
+package johan.command;
+
+import johan.storage.Storage;
+import johan.task.TaskList;
+import johan.ui.Ui;
+
+/**
+ * Command to sort tasks in the task list.
+ */
+public class SortCommand extends Command {
+ @Override
+ public void execute(TaskList tasks, Ui ui, Storage storage) {
+ tasks.sort();
+ ui.showMessage("Tasks sorted: deadlines by date, others by name.");
+ ui.showTaskList(tasks);
+ }
+}
diff --git a/src/main/java/johan/command/TodoCommand.java b/src/main/java/johan/command/TodoCommand.java
new file mode 100644
index 0000000000..ccfa543932
--- /dev/null
+++ b/src/main/java/johan/command/TodoCommand.java
@@ -0,0 +1,38 @@
+package johan.command;
+
+import johan.storage.Storage;
+import johan.task.Task;
+import johan.task.TaskList;
+import johan.task.Todo;
+import johan.ui.Ui;
+
+/**
+ * Command to add a todo task to the task list
+ */
+public class TodoCommand extends AddCommand {
+ /**
+ * Constructs a TodoCommand with the specified description.
+ * @param description The description of the todo task
+ */
+ public TodoCommand(String description) {
+ super(description);
+ }
+
+ /**
+ * Adds a todo task to the task list and updates storage.
+ * @param tasks The task list to modify
+ * @param ui The user interface for displaying output
+ * @param storage The storage system for persisting tasks
+ * @throws IllegalArgumentException If the description is empty
+ */
+ @Override
+ public void execute(TaskList tasks, Ui ui, Storage storage) {
+ if (description.isEmpty()) {
+ throw new IllegalArgumentException("The description of a todo cannot be empty.");
+ }
+ Task task = new Todo(description);
+ tasks.addTask(task);
+ ui.showTaskAdded(task, tasks.size());
+ storage.saveTasks(tasks.getTasks());
+ }
+}
diff --git a/src/main/java/johan/launcher/Launcher.java b/src/main/java/johan/launcher/Launcher.java
new file mode 100644
index 0000000000..68267c77f5
--- /dev/null
+++ b/src/main/java/johan/launcher/Launcher.java
@@ -0,0 +1,13 @@
+package johan.launcher;
+
+import javafx.application.Application;
+import johan.Main;
+
+/**
+ * A launcher class to workaround classpath issues with JavaFX.
+ */
+public class Launcher {
+ public static void main(String[] args) {
+ Application.launch(Main.class, args);
+ }
+}
diff --git a/src/main/java/johan/parser/Parser.java b/src/main/java/johan/parser/Parser.java
new file mode 100644
index 0000000000..6258653498
--- /dev/null
+++ b/src/main/java/johan/parser/Parser.java
@@ -0,0 +1,77 @@
+package johan.parser;
+
+import johan.command.Command;
+import johan.command.DeadlineCommand;
+import johan.command.DeleteCommand;
+import johan.command.EventCommand;
+import johan.command.ExitCommand;
+import johan.command.FindCommand;
+import johan.command.ListCommand;
+import johan.command.MarkCommand;
+import johan.command.OnDateCommand;
+import johan.command.SortCommand;
+import johan.command.TodoCommand;
+
+/**
+ * Parses user input into executable commands.
+ */
+public class Parser {
+ /**
+ * Parses the input string into a corresponding Command object.
+ *
+ * @param input The user input string to parse
+ * @return The corresponding Command object
+ * @throws Exception If the input cannot be parsed
+ */
+ public Command parse(String input) throws Exception {
+ if (input.equals("bye")) {
+ return new ExitCommand();
+ } else if (input.equals("list")) {
+ return new ListCommand();
+ } else if (input.startsWith("mark ")) {
+ int id = Integer.parseInt(input.substring(5)) - 1;
+ return new MarkCommand(id, true);
+ } else if (input.startsWith("unmark ")) {
+ int id = Integer.parseInt(input.substring(7)) - 1;
+ return new MarkCommand(id, false);
+ } else if (input.startsWith("todo ")) {
+ String desc = input.substring(5).trim();
+ return new TodoCommand(desc);
+ } else if (input.startsWith("deadline ")) {
+ int byIndex = input.indexOf("/by");
+ if (byIndex == -1) {
+ throw new IllegalArgumentException("Please specify a deadline with /by.");
+ }
+ String desc = input.substring(9, byIndex).trim();
+ String by = input.substring(byIndex + 4).trim();
+ return new DeadlineCommand(desc, by);
+ } else if (input.startsWith("event ")) {
+ int fromIndex = input.indexOf("/from");
+ int toIndex = input.indexOf("/to");
+ if (fromIndex == -1 || toIndex == -1) {
+ throw new IllegalArgumentException("Please specify /from and /to.");
+ }
+ String desc = input.substring(6, fromIndex).trim();
+ String from = input.substring(fromIndex + 6, toIndex).trim();
+ String to = input.substring(toIndex + 4).trim();
+ return new EventCommand(desc, from, to);
+ } else if (input.startsWith("delete ")) {
+ int id = Integer.parseInt(input.substring(7)) - 1;
+ return new DeleteCommand(id);
+ } else if (input.startsWith("on ")) {
+ String dateStr = input.substring(3).trim();
+ return new OnDateCommand(dateStr);
+ } else if (input.startsWith("find ")) {
+ String keyword = input.substring(5).trim();
+ if (keyword.isEmpty()) {
+ throw new IllegalArgumentException("Please specify a keyword.");
+ }
+ return new FindCommand(keyword);
+ } else if (input.startsWith("sort")) {
+ return new SortCommand();
+ } else {
+ throw new IllegalArgumentException("I'm sorry, but I don't know what that means :-(");
+ }
+ }
+}
+
diff --git a/src/main/java/johan/storage/Storage.java b/src/main/java/johan/storage/Storage.java
new file mode 100644
index 0000000000..885c67d6f6
--- /dev/null
+++ b/src/main/java/johan/storage/Storage.java
@@ -0,0 +1,152 @@
+package johan.storage;
+
+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.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+
+import johan.task.Deadline;
+import johan.task.Event;
+import johan.task.Task;
+import johan.task.Todo;
+
+
+/**
+ * Handles storage and retrieval of tasks to/from a file
+ */
+public class Storage {
+ private final String filePath;
+
+ /**
+ * Constructs a Storage instance with the specified file path.
+ * @param filePath The path to the storage file
+ */
+ public Storage(String filePath) {
+ this.filePath = filePath;
+ }
+
+ /**
+ * Saves the task list to the storage file.
+ * @param tasks The list of tasks to save
+ */
+ public void saveTasks(ArrayList tasks) {
+ File file = new File(filePath);
+ File directory = new File(file.getParent());
+
+ if (!directory.exists()) {
+ directory.mkdirs(); // Ensure directory exists
+ }
+
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
+ for (Task task : tasks) {
+ writer.write(formatTaskForSaving(task));
+ writer.newLine();
+ }
+ } catch (IOException e) {
+ System.out.println("Error saving tasks: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Loads tasks from the storage files.
+ * @return The list of loaded tasks
+ */
+ public ArrayList loadTasks() {
+ ArrayList tasks = new ArrayList<>();
+ File file = new File(filePath);
+
+ if (!file.exists()) {
+ System.out.println("No saved tasks found. Starting fresh.");
+ return tasks;
+ }
+
+ try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
+ String line;
+ // DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern("d/M/yyyy HHmm");
+ while ((line = reader.readLine()) != null) {
+ String[] parts = line.split(" \\| ");
+ if (parts.length < 3) {
+ continue;
+ }
+
+ String type = parts[0];
+ boolean isDone = parts[1].equals("1");
+ String description = parts[2];
+
+ Task task;
+ if (type.equals("T")) {
+ task = new Todo(description);
+ } else if (type.equals("D") && parts.length == 4) {
+ try {
+ // LocalDate deadline = LocalDate.parse(parts[3], inputFormatter);
+ task = new Deadline(description, parts[3]);
+ } catch (Exception e) {
+ System.out.println("Error parsing deadline for task: " + description + ". Skipping...");
+ continue;
+ }
+ } else if (type.equals("E") && parts.length == 5) {
+ try {
+ // LocalDate from = LocalDate.parse(parts[3], inputFormatter);
+ // LocalDate to = LocalDate.parse(parts[4], inputFormatter);
+ task = new Event(description, parts[3], parts[4]);
+ } catch (Exception e) {
+ System.out.println("Error parsing deadline for task: " + description + ". Skipping...");
+ continue;
+ }
+ } else {
+ continue; // Ignore invalid entries
+ }
+
+ if (isDone) {
+ task.markAsDone();
+ }
+
+ tasks.add(task);
+ }
+ } catch (IOException e) {
+ System.out.println("Error loading tasks: " + e.getMessage());
+ }
+
+ return tasks;
+ }
+
+ /**
+ * Formats a task for saving to the file.
+ * @param task The task to format
+ * @return The formatted string representation
+ */
+ private String formatTaskForSaving(Task task) {
+ String type;
+ if (task instanceof Todo) {
+ type = "T";
+ } else if (task instanceof Deadline) {
+ type = "D";
+ } else if (task instanceof Event) {
+ type = "E";
+ } else {
+ return "";
+ }
+
+ String status = task.isDone() ? "1" : "0";
+ String description = task.getDescription();
+
+ if (task instanceof Deadline) {
+ LocalDate deadline = ((Deadline) task).getBy();
+ return type + " | " + status + " | " + description + " | "
+ + deadline.format(DateTimeFormatter.ofPattern("d/M/yyyy"));
+ } else if (task instanceof Event) {
+ LocalDate startDate = ((Event) task).getStartDate();
+ LocalDate endDate = ((Event) task).getEndDate();
+ return type + " | " + status + " | " + description + " | "
+ + startDate.format(DateTimeFormatter.ofPattern("d/M/yyyy")) + " | "
+ + endDate.format(DateTimeFormatter.ofPattern("d/M/yyyy"));
+ } else {
+ return type + " | " + status + " | " + description;
+ }
+ }
+}
diff --git a/src/main/java/johan/task/Deadline.java b/src/main/java/johan/task/Deadline.java
new file mode 100644
index 0000000000..3c910086bb
--- /dev/null
+++ b/src/main/java/johan/task/Deadline.java
@@ -0,0 +1,62 @@
+package johan.task;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Represents a task with a deadline.
+ */
+public class Deadline extends Task {
+
+ protected LocalDate by;
+
+ /**
+ * Constructs a Deadline task with the specified description and deadline.
+ * @param description The task description
+ * @param by The deadline date string
+ */
+ public Deadline(String description, String by) {
+ super(description);
+ // this.by = by;
+ // DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern("d/M/yyyy");
+ this.by = parseDate(by);
+ }
+
+ /**
+ * Parses a date string into a LocalDate object.
+ * @param dateString The date string to parse
+ * @return The parsed LocalDate
+ */
+ private static LocalDate parseDate(String dateString) {
+ try {
+ DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("d/M/yyyy").withLocale(java.util.Locale.ENGLISH);
+ return LocalDate.parse(dateString.trim(), formatter1);
+ } catch (Exception e) {
+ DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd")
+ .withLocale(java.util.Locale.ENGLISH);
+ return LocalDate.parse(dateString.trim(), formatter2);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "[D]" + super.toString() + " (by: " + by.format(DateTimeFormatter.ofPattern("MMM dd yyyy")) + ")";
+ }
+
+ public LocalDate getBy() {
+ return by;
+ }
+ /**
+ * Compares deadlines chronologically by due date.
+ *
+ * @param other The other task to compare to
+ * @return Negative if this deadline is earlier, positive if later, zero if same
+ */
+ @Override
+ public int compareTo(Task other) {
+ if (other instanceof Deadline) {
+ return this.by.compareTo(((Deadline) other).by);
+ }
+ return super.compareTo(other);
+ }
+}
diff --git a/src/main/java/johan/task/Event.java b/src/main/java/johan/task/Event.java
new file mode 100644
index 0000000000..acd2bbe199
--- /dev/null
+++ b/src/main/java/johan/task/Event.java
@@ -0,0 +1,72 @@
+package johan.task;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Represents a task with a start and end date/time.
+ */
+public class Event extends Task {
+
+ protected LocalDate startDate;
+ protected LocalDate endDate;
+
+ /**
+ * Constructs an Event task with the specified description and time range.
+ * @param description The task description
+ * @param startDate The start date string
+ * @param endDate The end date string
+ */
+ public Event(String description, String startDate, String endDate) {
+ super(description);
+ // DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern("d/M/yyyy");
+ // this.startDate = LocalDate.parse(startDate, inputFormatter);
+ // this.endDate = LocalDate.parse(endDate, inputFormatter);
+ this.startDate = parseDate(startDate);
+ this.endDate = parseDate(endDate);
+ }
+
+ /**
+ * Parses a date string into a LocalDate object.
+ * @param dateString The date string to parse
+ * @return The parsed LocalDate
+ */
+ private static LocalDate parseDate(String dateString) {
+ try {
+ DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("d/M/yyyy")
+ .withLocale(java.util.Locale.ENGLISH);
+ return LocalDate.parse(dateString.trim(), formatter1);
+ } catch (Exception e) {
+ DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd")
+ .withLocale(java.util.Locale.ENGLISH);
+ return LocalDate.parse(dateString.trim(), formatter2);
+ }
+ }
+
+ /**
+ * Returns a string representation of the event task.
+ * @return The formatted string
+ */
+ @Override
+ public String toString() {
+ return "[E]" + super.toString()
+ + " (from: " + startDate.format(DateTimeFormatter.ofPattern("MMM dd yyyy"))
+ + " to: " + endDate.format(DateTimeFormatter.ofPattern("MMM dd yyyy")) + ")";
+ }
+
+ /**
+ * Gets the start date of the event.
+ * @return The start date as a LocalDate
+ */
+ public LocalDate getStartDate() {
+ return startDate;
+ }
+
+ /**
+ * Gets the end date of the event.
+ * @return The end date as a LocalDate
+ */
+ public LocalDate getEndDate() {
+ return endDate;
+ }
+}
diff --git a/src/main/java/johan/task/Task.java b/src/main/java/johan/task/Task.java
new file mode 100644
index 0000000000..6aeb305a23
--- /dev/null
+++ b/src/main/java/johan/task/Task.java
@@ -0,0 +1,109 @@
+package johan.task;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Abstract base class representing a task in the application.
+ */
+public abstract class Task implements Comparable {
+ private static int nextTaskID = 1;
+ protected String description;
+ protected boolean isDone;
+ protected LocalDate deadline;
+ private final int id;
+ /**
+ * Constructs a Task with the specified description.
+ * @param description The task description
+ */
+ public Task(String description) {
+ this.description = description;
+ this.isDone = false;
+ this.id = nextTaskID++;
+ this.deadline = null;
+ }
+
+ /**
+ * Gets the unique ID of the task as a string.
+ *
+ * @return The task ID
+ */
+ public String getID() {
+ return Integer.toString(this.id);
+ }
+ /**
+ * Gets the status icon representing whether the task is done.
+ *
+ * @return "X" if done, " " if not done
+ */
+ public String getStatusIcon() {
+ return (isDone ? "X" : " "); // mark done task with X
+ }
+ /**
+ * Marks the task as completed.
+ */
+ public void markAsDone() {
+ isDone = true;
+ }
+ /**
+ * Marks the task as not completed.
+ */
+ public void markAsNotDone() {
+ isDone = false;
+ }
+ /**
+ * Gets the deadline of the task, if any.
+ *
+ * @return The deadline date, or null if not set
+ */
+ public LocalDate getDeadline() {
+ return deadline;
+ }
+ /**
+ * Sets the deadline for the task.
+ *
+ * @param deadline The deadline date to set
+ */
+ public void setDeadline(LocalDate deadline) {
+ this.deadline = deadline;
+ }
+ /**
+ * Checks if the task is marked as done.
+ *
+ * @return true if the task is done, false otherwise
+ */
+ public boolean isDone() {
+ return isDone;
+ }
+ /**
+ * Gets the description of the task.
+ *
+ * @return The task description
+ */
+ public String getDescription() {
+ return description;
+ }
+ /**
+ * Returns a string representation of the task.
+ *
+ * @return The formatted string including status and deadline if present
+ */
+ @Override
+ public String toString() {
+ String baseString = "[" + this.getStatusIcon() + "] " + description;
+ if (deadline != null) {
+ baseString += "(by: " + deadline.format(DateTimeFormatter.ofPattern("MMM dd yyyy")) + ")";
+ }
+ return baseString;
+ }
+ /**
+ * Compares tasks alphabetically by description as a default.
+ *
+ * @param other The other task to compare to
+ * @return Negative if this task comes before, positive if after, zero if equal
+ */
+ @Override
+ public int compareTo(Task other) {
+ return this.description.compareTo(other.description);
+ }
+}
diff --git a/src/main/java/johan/task/TaskList.java b/src/main/java/johan/task/TaskList.java
new file mode 100644
index 0000000000..a820e38645
--- /dev/null
+++ b/src/main/java/johan/task/TaskList.java
@@ -0,0 +1,75 @@
+package johan.task;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Manages a list of tasks.
+ */
+public class TaskList {
+ private final ArrayList tasks;
+ /**
+ * Constructs a TaskList with the specified initial tasks.
+ *
+ * @param tasks The initial list of tasks
+ */
+ public TaskList(ArrayList tasks) {
+ this.tasks = tasks;
+ }
+ /**
+ * Adds a task to the list.
+ *
+ * @param task The task to add
+ */
+ public void addTask(Task task) {
+ tasks.add(task);
+ }
+ /**
+ * Deletes a task at the specified index.
+ *
+ * @param index The zero-based index of the task to delete
+ * @return The deleted task
+ * @throws IllegalArgumentException If the index is invalid
+ */
+ public Task deleteTask(int index) {
+ if (index >= 0 && index < tasks.size()) {
+ return tasks.remove(index);
+ }
+ throw new IllegalArgumentException("Invalid task index.");
+ }
+ /**
+ * Gets the task at the specified index.
+ *
+ * @param index The zero-based index of the task to retrieve
+ * @return The task at the specified index
+ * @throws IllegalArgumentException If the index is invalid
+ */
+ public Task getTask(int index) {
+ if (index >= 0 && index < tasks.size()) {
+ return tasks.get(index);
+ }
+ throw new IllegalArgumentException("Invalid task index.");
+ }
+ /**
+ * Gets the complete list of tasks.
+ *
+ * @return The ArrayList containing all tasks
+ */
+ public ArrayList getTasks() {
+ return tasks;
+ }
+ /**
+ * Gets the number of tasks in the list.
+ *
+ * @return The size of the task list
+ */
+ public int size() {
+ return tasks.size();
+ }
+ /**
+ * Sorts the task list chronologically for deadlines, alphabetically otherwise.
+ */
+ public void sort() {
+ Collections.sort(tasks);
+ }
+}
diff --git a/src/main/java/johan/task/Todo.java b/src/main/java/johan/task/Todo.java
new file mode 100644
index 0000000000..5240865f85
--- /dev/null
+++ b/src/main/java/johan/task/Todo.java
@@ -0,0 +1,23 @@
+package johan.task;
+/**
+ * Represents a simple todo task without a deadline.
+ */
+public class Todo extends Task {
+ /**
+ * Constructs a Todo task with the specified description.
+ *
+ * @param description The task description
+ */
+ public Todo(String description) {
+ super(description);
+ }
+ /**
+ * Returns a string representation of the todo task.
+ *
+ * @return The formatted string with "[T]" prefix
+ */
+ @Override
+ public String toString() {
+ return "[T]" + super.toString();
+ }
+}
diff --git a/src/main/java/johan/ui/Ui.java b/src/main/java/johan/ui/Ui.java
new file mode 100644
index 0000000000..ad376da988
--- /dev/null
+++ b/src/main/java/johan/ui/Ui.java
@@ -0,0 +1,172 @@
+package johan.ui;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Scanner;
+import java.util.function.Consumer;
+
+import johan.task.Deadline;
+import johan.task.Event;
+import johan.task.Task;
+import johan.task.TaskList;
+
+/**
+ * Handles user interface interactions for the Johan task management application.
+ */
+public class Ui {
+ private final Scanner scanner = new Scanner(System.in);
+ private final Consumer outputConsumer;
+
+ public Ui() {
+ this.outputConsumer = System.out::println;
+ }
+ public Ui(Consumer outputConsumer) {
+ this.outputConsumer = outputConsumer;
+ }
+ /**
+ * Displays a welcome message to the user.
+ */
+ public void showWelcome() {
+ outputConsumer.accept("Hello! I'm johan.Johan");
+ outputConsumer.accept("What can I do for you?");
+ outputConsumer.accept("Type 'list' or 'sort' to get started!");
+ }
+ /**
+ * Displays a goodbye message to the user.
+ */
+ public void showGoodbye() {
+ outputConsumer.accept("Bye. Hope to see you again soon!");
+ }
+ /**
+ * Displays the list of tasks in the provided TaskList.
+ *
+ * @param tasks The TaskList containing tasks to display
+ */
+ public void showTaskList(TaskList tasks) {
+ outputConsumer.accept("Here are the tasks in your list:");
+ for (int i = 0; i < tasks.size(); i++) {
+ outputConsumer.accept((i + 1) + "." + tasks.getTask(i).toString());
+ }
+ }
+ /**
+ * Displays a confirmation message for a newly added task.
+ *
+ * @param task The task that was added
+ * @param taskCount The total number of tasks in the list after adding
+ */
+ public void showTaskAdded(Task task, int taskCount) {
+ outputConsumer.accept("Got it. I've added this task:");
+ outputConsumer.accept(task.toString());
+ outputConsumer.accept("Now you have " + taskCount + " tasks in the list.");
+ }
+ /**
+ * Displays a confirmation message when a task's completion status is changed.
+ *
+ * @param task The task whose status was updated
+ * @param isDone True if the task was marked as done, false if marked as not done
+ */
+ public void showTaskMarked(Task task, boolean isDone) {
+ if (isDone) {
+ outputConsumer.accept("Nice! I've marked this task as done:");
+ } else {
+ outputConsumer.accept("OK, I've marked this task as not done yet:");
+ }
+ outputConsumer.accept(task.toString());
+ }
+ /**
+ * Displays a confirmation message when a task is deleted.
+ *
+ * @param task The task that was removed
+ * @param taskCount The total number of tasks remaining in the list
+ */
+ public void showTaskDeleted(Task task, int taskCount) {
+ outputConsumer.accept("____________________________________________________________");
+ outputConsumer.accept("Noted. I've removed this task:");
+ outputConsumer.accept(task.toString());
+ outputConsumer.accept("Now you have " + taskCount + " tasks in the list.");
+ outputConsumer.accept("____________________________________________________________");
+ }
+ /**
+ * Displays an error message to the user.
+ *
+ * @param message The error message to display
+ */
+ public void showError(String message) {
+ outputConsumer.accept("____________________________________________________________");
+ outputConsumer.accept(" OOPS!!! " + message);
+ outputConsumer.accept("____________________________________________________________");
+ }
+ /**
+ * Reads a command from the user input.
+ *
+ * @return The user's command as a lowercase, trimmed string
+ */
+ public String readCommand() {
+ return scanner.nextLine().toLowerCase().trim();
+ }
+ /**
+ * Displays a horizontal line as a visual separator.
+ */
+ public void showLine() {
+ outputConsumer.accept("____________________________________________________________");
+ }
+ /**
+ * Displays tasks occurring on the specified date from the TaskList.
+ *
+ * @param tasks The TaskList to search for tasks
+ * @param targetDate The date to filter tasks by
+ */
+ public void showTasksOnDate(TaskList tasks, LocalDate targetDate) {
+ outputConsumer.accept("Tasks on " + targetDate.format(DateTimeFormatter.ofPattern("d/MM/yyyy")) + ":");
+ boolean found = false;
+ for (int i = 0; i < tasks.size(); i++) {
+ Task task = tasks.getTask(i);
+ if (task instanceof Deadline) {
+ LocalDate deadline = ((Deadline) task).getBy();
+ if (deadline != null && deadline.equals(targetDate)) {
+ outputConsumer.accept((i + 1) + "." + task.toString());
+ found = true;
+ }
+ } else if (task instanceof Event) {
+ LocalDate startDate = ((Event) task).getStartDate();
+ LocalDate endDate = ((Event) task).getEndDate();
+ if (startDate != null && endDate != null && !startDate.isAfter(targetDate)
+ && !endDate.isBefore(targetDate)) {
+ outputConsumer.accept((i + 1) + "." + task.toString());
+ found = true;
+ }
+ }
+ }
+ if (!found) {
+ outputConsumer.accept("No tasks found on this date.");
+ }
+ }
+
+ /**
+ * Displays tasks whose descriptions contain the search keyword.
+ *
+ * @param matchingTasks The list of tasks that match the keyword
+ */
+ public void showFoundTasks(ArrayList matchingTasks) {
+ System.out.println("____________________________________________________________");
+ System.out.println(" Here are the matching tasks in your list:");
+ if (matchingTasks.isEmpty()) {
+ System.out.println(" No matching tasks found.");
+ } else {
+ for (int i = 0; i < matchingTasks.size(); i++) {
+ System.out.println(" " + (i + 1) + "." + matchingTasks.get(i).toString());
+ }
+ }
+ System.out.println("____________________________________________________________");
+ }
+ /**
+ * Displays a generic message to the user.
+ *
+ * @param message The message to display
+ */
+ public void showMessage(String message) {
+ outputConsumer.accept(message);
+ }
+}
+
diff --git a/src/main/resources/images/Johan.png b/src/main/resources/images/Johan.png
new file mode 100644
index 0000000000..a1954acf6d
Binary files /dev/null and b/src/main/resources/images/Johan.png differ
diff --git a/src/main/resources/images/User.png b/src/main/resources/images/User.png
new file mode 100644
index 0000000000..76c6643ee2
Binary files /dev/null and b/src/main/resources/images/User.png differ
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 0000000000..a855ddb7f8
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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..1eb272351e
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/java/johan/JohanTest.java b/src/test/java/johan/JohanTest.java
new file mode 100644
index 0000000000..bd577256a6
--- /dev/null
+++ b/src/test/java/johan/JohanTest.java
@@ -0,0 +1,16 @@
+//package johan;
+//
+//import org.junit.jupiter.api.Test;
+//import static org.junit.jupiter.api.Assertions.assertEquals;
+//
+//class JohanTest {
+// @Test
+// public void dummyTest() {
+// assertEquals(true, true);
+// }
+//
+// @Test
+// public void anotherDummyTest() {
+// assertEquals(4, 4);
+// }
+//}
diff --git a/src/test/java/johan/parser/ParserTest.java b/src/test/java/johan/parser/ParserTest.java
new file mode 100644
index 0000000000..acf9cb7b42
--- /dev/null
+++ b/src/test/java/johan/parser/ParserTest.java
@@ -0,0 +1,33 @@
+package johan.parser;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+import johan.command.Command;
+import johan.command.TodoCommand;
+
+/**
+ * Tests for the Parser class.
+ */
+public class ParserTest {
+ @Test
+ void parse_todoCommand_returnsTodoCommand() throws Exception {
+ Parser parser = new Parser();
+ Command command = parser.parse("todo read book");
+ assertTrue(command instanceof TodoCommand, "Should return TodoCommand for 'todo' input");
+ }
+
+ @Test
+ void parse_invalidInput_throwsException() {
+ Parser parser = new Parser();
+ assertThrows(Exception.class, () -> parser.parse("invalid garbage"), "Should throw for invalid input");
+ }
+
+ @Test
+ void parse_emptyInput_throwsException() {
+ Parser parser = new Parser();
+ assertThrows(Exception.class, () -> parser.parse(""), "Should throw for empty input");
+ }
+}
diff --git a/src/test/java/johan/ui/UiTest.java b/src/test/java/johan/ui/UiTest.java
new file mode 100644
index 0000000000..61ab72b38d
--- /dev/null
+++ b/src/test/java/johan/ui/UiTest.java
@@ -0,0 +1,53 @@
+package johan.ui;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for the Ui class.
+ */
+public class UiTest {
+ private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ @BeforeEach
+ void setUp() {
+ System.setOut(new PrintStream(outContent));
+ }
+
+ @AfterEach
+ void tearDown() {
+ System.setOut(originalOut);
+ }
+
+ @Test
+ void readCommand_validInput_returnsInput() {
+ // Simulate user input
+ String input = "todo read book\n";
+ System.setIn(new ByteArrayInputStream(input.getBytes()));
+
+ Ui ui = new Ui();
+ String result = ui.readCommand();
+
+ assertEquals("todo read book", result.trim(), "Should return trimmed user input");
+ }
+
+ @Test
+ void readCommand_emptyInput_returnsEmpty() {
+ // Simulate empty input
+ String input = "\n";
+ System.setIn(new ByteArrayInputStream(input.getBytes()));
+
+ Ui ui = new Ui();
+ String result = ui.readCommand();
+
+ assertEquals("", result.trim(), "Should return empty string for empty input");
+ }
+}
diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat
index 0873744649..79697abb68 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 johan.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
index c9ec870033..9ac25484da
--- a/text-ui-test/runtest.sh
+++ b/text-ui-test/runtest.sh
@@ -20,7 +20,7 @@ then
fi
# 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 Johan < input.txt > ACTUAL.TXT
# convert to UNIX format
cp EXPECTED.TXT EXPECTED-UNIX.TXT
@@ -35,4 +35,4 @@ then
else
echo "Test result: FAILED"
exit 1
-fi
\ No newline at end of file
+fi