diff --git a/.github/workflows/gradlew.yml b/.github/workflows/gradlew.yml new file mode 100644 index 0000000000..391c46b4fe --- /dev/null +++ b/.github/workflows/gradlew.yml @@ -0,0 +1,34 @@ +name: Java CI + +on: [push, pull_request] + +jobs: + build: + strategy: + matrix: + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + + steps: + - name: Set up repository + uses: actions/checkout@master + + - name: Set up repository + uses: actions/checkout@master + with: + ref: master + + - name: Merge to master + run: git checkout --progress --force ${{ github.sha }} + + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v1 + + - name: Setup JDK 11 + uses: actions/setup-java@v1 + with: + java-version: '11' + java-package: jdk+fx + + - name: Build and check with Gradle + run: ./gradlew check diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..eebbc9bcfc --- /dev/null +++ b/build.gradle @@ -0,0 +1,61 @@ +plugins { + id 'java' + id 'application' + id 'checkstyle' + id 'com.github.johnrengelman.shadow' version '5.1.0' +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0' + + String javaFxVersion = '11' + + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + +application { + mainClassName = "duke.Launcher" +} + +checkstyle { + toolVersion = '10.2' +} + +shadowJar { + archiveBaseName = "duke" + archiveClassifier = null +} + +run{ + standardInput = System.in +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..fb88cedfc2 --- /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/docs/README.md b/docs/README.md index 8077118ebe..e77bd96dc9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,217 @@ # User Guide +Duke chatbot is a GUI based application with basic task management functions. + ## Features -### Feature-ABC +### List all tasks `list` + +Lists all tasks are stored. + +### Add todo task `todo` + +Adds a todo task with a description. + +### Add deadline task `deadline` + +Adds a deadline task with a description and deadline datetime. + +### Add event task `event` + +Adds an event task with a description, start datetime and end datetime. + +### Delete a task `delete` + +Deletes a specific task. + +### Find tasks `find` + +Finds all tasks that contain the user input. + +### Mark task `mark` -Description of the feature. +Marks a specific task. -### Feature-XYZ +### Unmark task `mark` -Description of the feature. +Unmarks a specific task. + +### Explanation of commands `help` + +Displays all commands explanation. + +### Exit program `bye` + +Exit the program. ## Usage -### `Keyword` - Describe action +### `list` - list + +Lists all tasks are stored. + +Example of usage: +`list` + +Expected outcome: + +``` +Current tasks are: +1.[T][] Read Operating System Book +2.[D][] CS2109S Quiz (by:Feb 17 2023 23:59) +``` +### `todo` - todo TaskDescription + +Adds a todo task. + +Example of usage: +`todo Buy Book` -Describe the action and its outcome. +Expected outcome: + +``` +Got it, I've added this task: + [T][] Buy Book +Now you have 1 tasks in the list +``` +### `deadline` - deadline TaskDescription /by yyyy-mm-dd HHmm + +Adds a deadline task. Example of usage: +`deadline CS2109S Quiz /by 2023-02-17 2359` + +Expected outcome: + +``` +Got it. I've added this task: + [D][] CS2109S Quiz (by:Feb 17 2023 23:59) +Now you have 2 tasks in the list +``` +### `event` - event TaskDescription /from yyyy-mm-dd HHmm /to yyyy-mm-dd HHmm -`keyword (optional arguments)` +Adds an event task. + +Example of usage: +`event Tennis Party /from 2023-02-17 0800 /to 2023-02-18 1800` Expected outcome: -Description of the outcome. +``` +Got it. I've added this task: + [E][] Tennis Party (from:Feb 17 2023 08:00 to:Feb 18 2023 18:00) +Now you have 2 tasks in the list +``` +### `delete` - delete TaskIndex + +Deletes a specific task. + +Example of usage: +`delete 1` + +Expected outcome: ``` -expected output +Noted, I've removed this task: + [T][] Buy Book +Now you have 1 tasks in the list ``` +### `find` - delete TaskDescription + +Finds all tasks that contain task description. + +Example of usage: +`find Book` + +Expected outcome: + +``` +1.[T][] Read Operating System Book +2.[T][] Buy Book +``` +### `mark` - mark TaskIndex + +Marks a specific task. + +Example of usage: +`mark 1` + +Expected outcome: + +``` +Nice! I've marked this task as done: + [T][X] Read Operating System Book +``` +### `unmark` - unmark TaskIndex + +Unmarks a specific task. + +Example of usage: +`unmark 1` + +Expected outcome: + +``` +OK, I've marked this task as not done yet: + [T][] Read Operating System Book +``` +### `help` - help + +Displays explanations about all commands. + +Example of usage: +`help` + +Expected outcome: + +``` +Available commands: + +-help: show the list of all commands. + e.g. "help" + +-bye: exit the application. + e.g. "bye" + +-list: list all the tasks that are stored. + e.g. "list" + +-todo: add a todo task with a description. + e.g. "todo borrow book" + +-deadline: add a deadline task with description and due date time (after "/by"). + e.g. "deadline watch lecture /by 2019-09-08 1900" + +-event: add an event task with description, start date time (after "/from") + and end date time (after "/to"). + e.g. "event Chinese New Year /from 2023-02-12 0000 /to 2023-02-18 0000" + +-find: find any tasks contains the description, support partially search. + e.g. "find book" "find bo" + +-delete: delete the specific task based on the task number. + e.g. "delete 2" + +-mark: mark the specific task as completed based on the task number. + e.g. "mark 3" + +-unmark: unmark the specific task as not completed based on the taks number. + e.g. "unmark 5" + +Note: 1. all the date time format is "yyyy-mm-dd HHmm". + 2. task number must be within the task list size. + 3. storage file at "./src/data/duke.txt" +``` +### `bye` - bye + +Exits the program. + +Example of usage: +`bye` + +Expected outcome: + +``` +Bye. Hope to see you again soon! +``` + diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..f06e120669 Binary files /dev/null and b/docs/Ui.png differ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..f3d88b1c2f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..b7c8c5dbf5 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..2fe81a7d95 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..62bd9b9cce --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,103 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334cc..0000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} diff --git a/src/main/java/command/AddDeadlineCommand.java b/src/main/java/command/AddDeadlineCommand.java new file mode 100644 index 0000000000..77b8682433 --- /dev/null +++ b/src/main/java/command/AddDeadlineCommand.java @@ -0,0 +1,40 @@ +package command; + +import dukeexception.DukeException; +import storage.Storage; +import storage.TaskList; +import task.Deadline; +import userinteraction.Ui; + +/** + * Command class for adding deadline tasks. + */ +public class AddDeadlineCommand extends AddTaskCommand { + /** + * Class constructor. + * + * @param input String from a user input. + */ + public AddDeadlineCommand(String input) { + super(input); + + } + + /** + * Creates a deadline task, adds it to the task list and saves it to the file. + * + * @param taskList Stores all tasks. + * @param ui The Ui to be used for printing messages. + * @param storage Saves all tasks in a file. + * @return Returns the String message about adding a deadline task. + * @throws DukeException Checks the validation of input. + */ + @Override + public String process(TaskList taskList, Ui ui, Storage storage) throws DukeException { + Deadline deadline = Deadline.generate(this.getInput()); + taskList.addTask(deadline); + storage.saveData(taskList); + return ui.printAddTaskMsg(taskList, deadline); + } +} + diff --git a/src/main/java/command/AddEventCommand.java b/src/main/java/command/AddEventCommand.java new file mode 100644 index 0000000000..26e76b3236 --- /dev/null +++ b/src/main/java/command/AddEventCommand.java @@ -0,0 +1,38 @@ +package command; + +import dukeexception.DukeException; +import storage.Storage; +import storage.TaskList; +import task.Event; +import userinteraction.Ui; + +/** + * Command class for adding event tasks. + */ +public class AddEventCommand extends AddTaskCommand { + /** + * Class constructor. + * + * @param input String from a user input. + */ + public AddEventCommand(String input) { + super(input); + } + + /** + * Creates a deadline task, adds it to the task list and saves it to the file. + * + * @param taskList Stores all tasks. + * @param ui The Ui to be used for printing messages. + * @param storage Saves all tasks in a file. + * @return Returns the String message about adding an event task. + * @throws DukeException Checks the validation of input. + */ + @Override + public String process(TaskList taskList, Ui ui, Storage storage) throws DukeException { + Event event = Event.generate(this.getInput()); + taskList.addTask(event); + storage.saveData(taskList); + return ui.printAddTaskMsg(taskList, event); + } +} diff --git a/src/main/java/command/AddTaskCommand.java b/src/main/java/command/AddTaskCommand.java new file mode 100644 index 0000000000..c082845018 --- /dev/null +++ b/src/main/java/command/AddTaskCommand.java @@ -0,0 +1,44 @@ +package command; + +import dukeexception.DukeException; +import storage.Storage; +import storage.TaskList; +import userinteraction.Ui; + +/** + * Abstract class for adding tasks. + */ +abstract class AddTaskCommand extends Command { + /** + * Class constructor. + * + * @param input String from a user input. + */ + public AddTaskCommand(String input) { + super(input); + } + + /** + * Creates a task, adds it to the task list and saves it to the file. + * + * @param taskList Stores all tasks. + * @param ui The Ui to be used for printing messages. + * @param storage Saves all tasks in a file. + * @return Returns the String message about adding a task. + * @throws DukeException Checks the validation of input. + */ + @Override + public String process(TaskList taskList, Ui ui, Storage storage) throws DukeException { + return null; + } + + /** + * Checks whether exit the program. + * + * @return Boolean Exit the program. + */ + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/command/AddTodoCommand.java b/src/main/java/command/AddTodoCommand.java new file mode 100644 index 0000000000..c768649083 --- /dev/null +++ b/src/main/java/command/AddTodoCommand.java @@ -0,0 +1,39 @@ +package command; + +import dukeexception.DukeException; +import storage.Storage; +import storage.TaskList; +import task.Todo; +import userinteraction.Ui; + +/** + * Command class for adding todo tasks. + */ +public class AddTodoCommand extends AddTaskCommand { + + /** + * Class constructor. + * + * @param input String from a user input. + */ + public AddTodoCommand(String input) { + super(input); + } + + /** + * Creates a todo task, adds it to the task list and saves it to the file. + * + * @param taskList Stores all tasks. + * @param ui The Ui to be used for printing messages. + * @param storage Saves all tasks in a file. + * @return Returns the String message about adding a deadline task. + * @throws DukeException Checks the validation of input. + */ + @Override + public String process(TaskList taskList, Ui ui, Storage storage) throws DukeException { + Todo todo = Todo.generate(this.getInput()); + taskList.addTask(todo); + storage.saveData(taskList); + return ui.printAddTaskMsg(taskList, todo); + } +} diff --git a/src/main/java/command/Command.java b/src/main/java/command/Command.java new file mode 100644 index 0000000000..b643cf8d4f --- /dev/null +++ b/src/main/java/command/Command.java @@ -0,0 +1,43 @@ +package command; + +import dukeexception.DukeException; +import storage.Storage; +import storage.TaskList; +import userinteraction.Ui; + +/** + * Abstract class about users' commands. + */ +public abstract class Command { + private final String input; + /** + * Class constructor. + * + * @param input String from a user input. + */ + public Command(String input) { + this.input = input; + } + + public String getInput() { + return input; + } + + /** + * Creates a todo task, adds it to the task list and saves it to the file. + * + * @param taskList Stores all tasks. + * @param ui The Ui to be used for printing messages. + * @param storage Saves all tasks in a file. + * @return Returns the String message. + * @throws DukeException Checks the validation of input. + */ + public abstract String process(TaskList taskList, Ui ui, Storage storage) throws DukeException; + + /** + * Checks whether exit the program. + * + * @return Boolean Exit the program. + */ + public abstract boolean isExit(); +} diff --git a/src/main/java/command/DeleteTaskCommand.java b/src/main/java/command/DeleteTaskCommand.java new file mode 100644 index 0000000000..05112d8235 --- /dev/null +++ b/src/main/java/command/DeleteTaskCommand.java @@ -0,0 +1,44 @@ +package command; + +import dukeexception.DukeException; +import storage.Storage; +import storage.TaskList; +import userinteraction.Ui; + +/** + * Command class for adding deadline tasks. + */ +public class DeleteTaskCommand extends Command { + /** + * Class constructor. + * + * @param input String from a user input. + */ + public DeleteTaskCommand(String input) { + super(input); + } + + /** + * Deletes a task, both of task list and file. + * + * @param taskList Stores all tasks. + * @param ui The Ui to be used for printing messages. + * @param storage Deletes a task in a file. + * @return Returns the String message about deleting a task. + * @throws DukeException Checks the validation of input. + */ + @Override + public String process(TaskList taskList, Ui ui, Storage storage) throws DukeException { + return taskList.deleteTask(this.getInput(), ui, storage); + } + + /** + * Checks whether exit the program. + * + * @return Boolean Exit the program. + */ + @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..951e11785d --- /dev/null +++ b/src/main/java/command/ExitCommand.java @@ -0,0 +1,38 @@ +package command; + +import dukeexception.DukeException; +import storage.Storage; +import storage.TaskList; +import userinteraction.Ui; + +/** + * Command class for exiting the program. + */ +public class ExitCommand extends Command { + /** + * Class constructor. + * + * @param input String from a user input. + */ + public ExitCommand(String input) { + super(input); + } + + /** + * Exit the program. + * + * @param taskList Stores all tasks. + * @param ui The Ui to be used for printing messages. + * @param storage Deletes a task in a file. + * @return Returns the Bye message. + * @throws DukeException Checks the validation of input. + */ + @Override + public String process(TaskList taskList, Ui ui, Storage storage) { + return ui.printByeMsg(); + } + @Override + public boolean isExit() { + return true; + } +} diff --git a/src/main/java/command/FindTaskCommand.java b/src/main/java/command/FindTaskCommand.java new file mode 100644 index 0000000000..db4b82554b --- /dev/null +++ b/src/main/java/command/FindTaskCommand.java @@ -0,0 +1,44 @@ +package command; + +import dukeexception.DukeException; +import storage.Storage; +import storage.TaskList; +import userinteraction.Ui; + +/** + * Command class for finding some tasks. + */ +public class FindTaskCommand extends Command { + /** + * Class constructor. + * + * @param input String from a user input. + */ + public FindTaskCommand(String input) { + super(input); + } + + /** + * Finds any tasks that contain the description. + * + * @param taskList Stores all tasks. + * @param ui The Ui to be used for printing messages. + * @param storage Deletes a task in a file. + * @return Returns a list of tasks that contains the input. + * @throws DukeException Checks the validation of input. + */ + @Override + public String process(TaskList taskList, Ui ui, Storage storage) throws DukeException { + return taskList.findTask(this.getInput(), ui); + } + + /** + * Checks whether exit the program. + * + * @return Boolean Exit the program. + */ + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/command/HelpCommand.java b/src/main/java/command/HelpCommand.java new file mode 100644 index 0000000000..c3d78689a1 --- /dev/null +++ b/src/main/java/command/HelpCommand.java @@ -0,0 +1,45 @@ +package command; + +import dukeexception.DukeException; +import storage.Storage; +import storage.TaskList; +import userinteraction.Ui; + +/** + * Command class for listing all the available commands. + */ +public class HelpCommand extends Command { + + /** + * Class constructor. + * + * @param input String from a user input. + */ + public HelpCommand(String input) { + super(input); + } + + /** + * Lists all available commands. + * + * @param taskList Stores all tasks. + * @param ui The Ui to be used for printing messages. + * @param storage Saves all tasks in a file. + * @return Returns a list of available commands. + * @throws DukeException Checks the validation of input. + */ + @Override + public String process(TaskList taskList, Ui ui, Storage storage) throws DukeException { + return ui.printHelpMsg(storage); + } + + /** + * Checks whether exit the program. + * + * @return Boolean Exit the program. + */ + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/command/InvalidCommand.java b/src/main/java/command/InvalidCommand.java new file mode 100644 index 0000000000..f343dafc16 --- /dev/null +++ b/src/main/java/command/InvalidCommand.java @@ -0,0 +1,44 @@ +package command; + +import dukeexception.DukeException; +import storage.Storage; +import storage.TaskList; +import userinteraction.Ui; + +/** + * Command class for showing invalid command. + */ +public class InvalidCommand extends Command { + /** + * Class constructor. + * + * @param input String from a user input. + */ + public InvalidCommand(String input) { + super(input); + } + + /** + * Deletes a task, both of task list and file. + * + * @param taskList Stores all tasks. + * @param ui The Ui to be used for printing messages. + * @param storage Deletes a task in a file. + * @return Returns the invalid input message. + * @throws DukeException Checks the validation of input. + */ + @Override + public String process(TaskList taskList, Ui ui, Storage storage) throws DukeException { + return ui.printWrongMsg(); + } + + /** + * Checks whether exit the program. + * + * @return Boolean Exit the program. + */ + @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..e0db79fadb --- /dev/null +++ b/src/main/java/command/ListCommand.java @@ -0,0 +1,45 @@ +package command; + +import dukeexception.DukeException; +import storage.Storage; +import storage.TaskList; +import userinteraction.Ui; + +/** + * Command class for listing tasks. + */ +public class ListCommand extends Command { + + /** + * Class constructor. + * + * @param input String from a user input. + */ + public ListCommand(String input) { + super(input); + } + + /** + * Lists all tasks. + * + * @param taskList Stores all tasks. + * @param ui The Ui to be used for printing messages. + * @param storage Saves all tasks in a file. + * @return Returns a list of tasks are stored. + * @throws DukeException Checks the validation of input. + */ + @Override + public String process(TaskList taskList, Ui ui, Storage storage) throws DukeException { + return taskList.listTask(this.getInput()); + } + + /** + * Checks whether exit the program. + * + * @return Boolean Exit the program. + */ + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/command/MarkTaskCommand.java b/src/main/java/command/MarkTaskCommand.java new file mode 100644 index 0000000000..b81689f623 --- /dev/null +++ b/src/main/java/command/MarkTaskCommand.java @@ -0,0 +1,49 @@ +package command; + +import dukeexception.DukeException; +import storage.Storage; +import storage.TaskList; +import userinteraction.Ui; + +/** + * Command class for marking or unmarking tasks. + */ +public class MarkTaskCommand extends Command { + + private final boolean isMarked; + + /** + * Class constructor. + * + * @param input String from a user input. + * @param isMarked Boolean to mark or unmark the task. + */ + public MarkTaskCommand(String input, boolean isMarked) { + super(input); + this.isMarked = isMarked; + } + + /** + * Marks or unmarks a task. + * + * @param taskList Stores all tasks. + * @param ui The Ui to be used for printing messages. + * @param storage Saves all tasks in a file. + * @return Returns String message about marking or unmarking message. + * @throws DukeException Checks the validation of input. + */ + @Override + public String process(TaskList taskList, Ui ui, Storage storage) throws DukeException { + return taskList.markTask(isMarked, this.getInput(), ui, storage); + } + + /** + * Checks whether exit the program. + * + * @return Boolean Exit the program. + */ + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/duke/DialogBox.java b/src/main/java/duke/DialogBox.java new file mode 100644 index 0000000000..1956f36a58 --- /dev/null +++ b/src/main/java/duke/DialogBox.java @@ -0,0 +1,64 @@ +package duke; + +import java.io.IOException; +import java.util.Collections; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; + +/** + * 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); + dialog.setMinHeight(Region.USE_PREF_SIZE); + + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + Collections.reverse(tmp); + getChildren().setAll(tmp); + setAlignment(Pos.TOP_LEFT); + } + + public static DialogBox getUserDialog(String text, Image img) { + return new DialogBox(text, img); + } + + public static DialogBox getDukeDialog(String text, Image img) { + var db = new DialogBox(text, img); + db.flip(); + return db; + } +} diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java new file mode 100644 index 0000000000..0b046f2af4 --- /dev/null +++ b/src/main/java/duke/Duke.java @@ -0,0 +1,71 @@ +package duke; + +import command.Command; +import dukeexception.DukeException; +import storage.Storage; +import storage.TaskList; +import userinteraction.Parser; +import userinteraction.Ui; + +/** + * The main class of the program. + */ +public class Duke { + private static final String FILE_PATH = ".\\src\\data\\duke.txt"; + + private final Ui ui; + private final Storage storage; + private final TaskList taskList; + + /** + * Class constructor. + */ + public Duke() { + ui = new Ui(); + storage = new Storage(FILE_PATH); + taskList = storage.loadData(); + } + + /** + * Runs the whole program. + */ + public void run() { + boolean isExit = false; + while (!isExit) { + try { + String input = ui.readCommand(); + Command command = Parser.parse(input); + if (command != null) { + command.process(taskList, ui, storage); + isExit = command.isExit(); + } + } catch (DukeException e) { + System.out.println(e.getMessage()); + } + } + } + + public String getResponse(String input) { + try { + Command command = Parser.parse(input); + return command.process(taskList, ui, storage); + } catch (DukeException e) { + return e.getMessage(); + } + } + + public Ui getUi() { + return ui; + } + + /** + * Main method of the Duke. + * + * @param args String array argument + */ + public static void main(String[] args) { + Duke duke = new Duke(); + duke.run(); + } + +} diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java new file mode 100644 index 0000000000..dae57206e2 --- /dev/null +++ b/src/main/java/duke/Launcher.java @@ -0,0 +1,12 @@ +package duke; + +import javafx.application.Application; + +/** + * Launcher for the chatbot. + */ +public class Launcher { + public static void main(String[] args) { + Application.launch(Main.class, args); + } +} diff --git a/src/main/java/duke/Main.java b/src/main/java/duke/Main.java new file mode 100644 index 0000000000..a2cacb8d4f --- /dev/null +++ b/src/main/java/duke/Main.java @@ -0,0 +1,35 @@ +package duke; + + +import java.io.IOException; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + + +/** + * A GUI for Duke using FXML. + */ +public class Main extends Application { + + private Duke duke = new Duke(); + + @Override + public void start(Stage stage) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml")); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + stage.setTitle("Duke Chatbot"); + stage.setResizable(false); + stage.setScene(scene); + fxmlLoader.getController().setDuke(duke); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/duke/MainWindow.java b/src/main/java/duke/MainWindow.java new file mode 100644 index 0000000000..90d0fcccbe --- /dev/null +++ b/src/main/java/duke/MainWindow.java @@ -0,0 +1,71 @@ +package duke; + +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.util.Duration; + + +/** + * Controller for MainWindow. Provides the layout for the other controls. + */ +public class MainWindow extends AnchorPane { + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogContainer; + @FXML + private TextField userInput; + @FXML + private Button sendButton; + + private Duke duke; + + private Image userImage = new Image(this.getClass().getResourceAsStream("/images/DaUser.png")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png")); + + /** + * Delay of 2 second when the program is exiting. + */ + private final javafx.animation.PauseTransition delay = new javafx.animation.PauseTransition(Duration.seconds(1)); + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + } + + public void setDuke(Duke d) { + duke = d; + dialogContainer.getChildren().add(DialogBox.getDukeDialog( + duke.getUi().printWelcomeMsg(), dukeImage) + ); + dialogContainer.getChildren().add(DialogBox.getDukeDialog( + duke.getUi().printGetHelpMsg(), dukeImage) + ); + delay.setOnFinished(event -> Platform.exit()); + } + + /** + * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to + * the dialog container. Clears the user input after processing. + */ + @FXML + private void handleUserInput() { + String input = userInput.getText(); + String response = duke.getResponse(input); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(input, userImage), + DialogBox.getDukeDialog(response, dukeImage) + ); + String exitMsg = duke.getUi().printByeMsg(); + if (response.equals(exitMsg)) { + delay.play(); + } + userInput.clear(); + } +} diff --git a/src/main/java/dukeexception/DukeException.java b/src/main/java/dukeexception/DukeException.java new file mode 100644 index 0000000000..eb9fa5e6d6 --- /dev/null +++ b/src/main/java/dukeexception/DukeException.java @@ -0,0 +1,16 @@ +package dukeexception; + +/** + * Exception class. + */ +public class DukeException extends Exception { + + /** + * Class constructor. + * + * @param message Error message is to be printed. + */ + public DukeException(String message) { + super(message); + } +} diff --git a/src/main/java/storage/Storage.java b/src/main/java/storage/Storage.java new file mode 100644 index 0000000000..60ea8b620d --- /dev/null +++ b/src/main/java/storage/Storage.java @@ -0,0 +1,125 @@ +package storage; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import dukeexception.DukeException; +import task.Deadline; +import task.Event; +import task.Task; +import task.Todo; + + +/** + * Storage class to deal with the file. + */ +public class Storage { + + private final String filePath; + + /** + * Class constructor. + * + * @param filePath The path of a file to be stored. + */ + public Storage(String filePath) { + this.filePath = filePath; + checkFileExit(); + } + + /** + * Checks whether the file exits. + */ + public void checkFileExit() { + try { + Path file = Paths.get(".", filePath); + if (!Files.exists(file)) { + Files.createDirectories(file.getParent()); + Files.createFile(file); + } + } catch (IOException e) { + System.out.println("\t Invalid Path.\n"); + e.printStackTrace(); + } + } + + /** + * Reads the tasks from the file. + * + * @return Tasklist which is stored in the file. + */ + public TaskList loadData() { + ArrayList arrayList = new ArrayList<>(); + Path file = Paths.get(".", filePath); + try { + List taskLine = Files.readAllLines(file); + for (int i = 0; i < taskLine.size(); i++) { + String[] content = taskLine.get(i).split(" \\| "); + switch (content[0]) { + case "T": + arrayList.add(Todo.generateTask(content)); + break; + case "E": + arrayList.add(Event.generateTask(content)); + break; + case "D": + arrayList.add(Deadline.generateTask(content)); + break; + default: + throw new DukeException("\t Invalid data!\n"); + } + } + } catch (FileNotFoundException e) { + System.out.println("\t File not found.\n"); + } catch (DukeException e) { + System.out.println(e.getMessage()); + } catch (IOException e) { + System.out.println("\t Invalid Path.\n"); + } + return new TaskList(arrayList); + } + + + /** + * Saves tasks into the file. + * + * @param tasks Tasks are stored in the file. + */ + public void saveData(TaskList tasks) { + Path file = Paths.get(".", filePath); + ArrayList arrayList = tasks.getTasks(); + String data = ""; + try { + for (Task task : arrayList) { + data += task.storeTaskString() + "\n"; + } + Files.writeString(file, data); + } catch (IOException e) { + System.out.println("\t Invalid Path.\n"); + } + } + + /** + * Load all commands from help.txt. + * + * @return Returns all available commands. + */ + public String loadHelpExplanationFile() { + InputStream helpStream = this.getClass().getResourceAsStream("/explanation/help.txt"); + String explanation = ""; + try { + explanation = new String(helpStream.readAllBytes()); + } catch (FileNotFoundException e) { + System.out.println("\t File not found.\n"); + } catch (IOException e) { + System.out.println("\t Invalid Path.\n"); + } + return explanation; + } +} diff --git a/src/main/java/storage/TaskList.java b/src/main/java/storage/TaskList.java new file mode 100644 index 0000000000..ba58681db0 --- /dev/null +++ b/src/main/java/storage/TaskList.java @@ -0,0 +1,163 @@ +package storage; + +import java.util.ArrayList; + +import dukeexception.DukeException; +import task.Task; +import userinteraction.Ui; + +/** + * Task list class which stores all tasks. + */ +public class TaskList { + private final ArrayList tasks; + + /** + * Class constructor. + * + * @param tasks Arraylist that stores all tasks. + */ + public TaskList(ArrayList tasks) { + this.tasks = tasks; + } + + public int getSize() { + return tasks.size(); + } + public ArrayList getTasks() { + return tasks; + } + + /** + * List all the tasks. + * + * @param input User input. + * @return Returns a list of tasks are stored. + * @throws DukeException Checks the validation of input. + */ + public String listTask(String input) throws DukeException { + String[] inputLine = input.split(" ", 2); + if (inputLine.length > 1) { + throw new DukeException("\t OOPS!!! The format is invalid!\n"); + } + assert inputLine.length == 1; + String str = "Current tasks are: \n"; + for (int i = 0; i < tasks.size(); i++) { + str += "\t " + (i + 1) + ". " + tasks.get(i).toString() + "\n"; + } + if (tasks.size() == 0) { + return "There is no task yet!"; + } else { + return str; + } + } + + /** + * Adds a task into task list. + * + * @param task A task. + */ + public void addTask(Task task) { + tasks.add(task); + } + + /** + * Deletes a task in the task list. + * + * @param input User input + * @param ui The Ui to be used for printing messages. + * @param storage Deletes a task in a file. + * @return Returns delete message of specific task. + * @throws DukeException Checks the validation of input. + */ + public String deleteTask(String input, Ui ui, Storage storage) throws DukeException { + if (input.trim().equals("delete")) { + throw new DukeException("\t OOPS!!! The description of a delete cannot be empty.\n"); + } + String[] inputLine = input.split(" ", 2); + if (inputLine.length < 2) { + throw new DukeException("\t OOPS!!! The format is invalid, please give numbers.\n"); + } + assert inputLine.length == 2; + int taskIndex; + try { + taskIndex = Integer.parseInt(inputLine[1]); + } catch (NumberFormatException e) { + throw new DukeException("\t OOPS!!! Please input a valid number.\n"); + } + int taskListSize = this.getSize(); + if (taskIndex < 1 || taskIndex > taskListSize) { + throw new DukeException("\t OOPS!!! The index is out of range.\n"); + } + Task task = tasks.get(taskIndex - 1); + tasks.remove(taskIndex - 1); + storage.saveData(this); + return ui.printDeleteTaskMsg(task, tasks.size()); + } + + /** + * Marks or unmarks a task. + * + * @param isDone Boolean to mark or unmark a task. + * @param input User Input. + * @param ui The Ui to be used for printing messages. + * @return Returns mark or unmark task message. + * @throws DukeException Checks the validation of input. + */ + public String markTask(boolean isDone, String input, Ui ui, Storage storage) throws DukeException { + if (input.trim().equals("mark") || input.trim().equals("unmark")) { + throw new DukeException("\t OOPS!!! Please input a number.\n"); + } + String[] inputLine = input.split(" ", 2); + if (inputLine.length < 2) { + throw new DukeException("\t OOPS!!! The format is invalid, please give numbers.\n"); + } + assert inputLine.length == 2; + int taskIndex; + try { + taskIndex = Integer.parseInt(inputLine[1]) - 1; + } catch (NumberFormatException e) { + throw new DukeException("\t OOPS!!! Please input a valid number.\n"); + } + int taskListSize = tasks.size(); + if (taskIndex + 1 > taskListSize || taskIndex < 0) { + throw new DukeException("\t OOPS!!! The index is out of range.\n"); + } + Task task = tasks.get(taskIndex); + task.setDone(isDone); + storage.saveData(this); + return ui.printMarkTaskMsg(isDone, task); + } + + /** + * Finds the tasks which contains the same input descriptions. + * + * @param input User input + * @param ui The Ui to be used for printing messages. + * @return Returns a list of tasks whose contain the input. + * @throws DukeException Checks the validation of input. + */ + public String findTask(String input, Ui ui) throws DukeException { + if (input.trim().equals("find")) { + throw new DukeException("OOPS!!! The description of a find cannot be empty.\n"); + } + String[] inputLine = input.split(" ", 2); + if (inputLine.length < 2) { + throw new DukeException("OOPS!!! The format is invalid, please give a search information.\n"); + } + assert inputLine.length == 2; + boolean availableTasks = false; + String str = ui.printFindTaskMsg(); + for (int i = 0; i < tasks.size(); i++) { + if (tasks.get(i).getDescription().contains(inputLine[1])) { + availableTasks = true; + str += "\t " + (i + 1) + ". " + tasks.get(i).toString() + "\n"; + } + } + if (availableTasks) { + return str; + } else { + return "There is no matched tasks!"; + } + } +} diff --git a/src/main/java/task/Deadline.java b/src/main/java/task/Deadline.java new file mode 100644 index 0000000000..39e1d00790 --- /dev/null +++ b/src/main/java/task/Deadline.java @@ -0,0 +1,83 @@ +package task; + +import dukeexception.DukeException; +import utils.DateTime; + +/** + * Deadline class to deal with deadline tasks. + */ +public class Deadline extends Task { + private String dateTime; + + /** + * Class constructor. + * + * @param description The description of the task. + * @param isDone Marks or unmarks the task. + * @param dateTime Deadline date time. + */ + private Deadline(String description, boolean isDone, String dateTime) { + super(description, isDone); + this.dateTime = dateTime; + } + + /** + * Returns deadline task based on the user input. + * + * @param input User Input. + * @return Deadline task. + * @throws DukeException Checks the validation of input. + */ + public static Deadline generate(String input) throws DukeException { + if (input.trim().equals("deadline")) { + throw new DukeException("\t OOPS!!! The description of a deadline cannot be empty.\n"); + } + String[] inputLine = input.split(" ", 2); + if (inputLine.length < 2) { + throw new DukeException("\t OOPS!!! The description of a deadline cannot be empty.\n"); + } + String[] descriptions = inputLine[1].split(" /by ", 2); + if (descriptions.length < 2) { + throw new DukeException("\t OOPS!!! The date time of a deadline cannot be empty.\n"); + } + return new Deadline(descriptions[0], false, + DateTime.dateFormatter(descriptions[1])); + } + + /** + * Returns a deadline task from the file. + * + * @param taskLine Each line from the input file. + * @return Deadline task. + */ + public static Deadline generateTask(String[] taskLine) { + boolean check = taskLine[1].equals("1"); + return new Deadline(taskLine[2], check, taskLine[3]); + } + public String getDateTime() { + return dateTime; + } + @Override + public String getTaskType() { + return "D"; + } + @Override + public String storeTaskString() { + String str = this.getTaskType() + " | " + this.getMarkedString() + + " | " + this.getDescription() + " | " + this.getDateTime(); + return str; + } + @Override + public String toString() { + String discription = this.getDescription(); + boolean checked = this.isDone(); + String dateTime = this.getDateTime(); + String str = ""; + if (checked) { + str = "[D][X] " + discription + " (by: " + dateTime + ")"; + } else { + str = "[D][ ] " + discription + " (by: " + dateTime + ")"; + } + return str; + } +} diff --git a/src/main/java/task/Event.java b/src/main/java/task/Event.java new file mode 100644 index 0000000000..5785a5434c --- /dev/null +++ b/src/main/java/task/Event.java @@ -0,0 +1,99 @@ +package task; + +import dukeexception.DukeException; +import utils.DateTime; + +/** + * Event class to deal with event tasks. + */ +public class Event extends Task { + private final String start; + private final String end; + + /** + * Class constructor. + * + * @param description The description of the task. + * @param isDone Marks or unmarks the task. + * @param start Event start date time. + * @param start Event end date time. + */ + private Event(String description, boolean isDone, String start, String end) { + super(description, isDone); + this.start = start; + this.end = end; + } + + /** + * Returns event task based on the user input. + * + * @param input User Input. + * @return Event task. + * @throws DukeException Checks the validation of input. + */ + public static Event generate(String input) throws DukeException { + if (input.trim().equals("event")) { + throw new DukeException("\t OOPS!!! The description of a event cannot be empty.\n"); + } + String[] inputLine = input.split(" ", 2); + if (inputLine.length < 2) { + throw new DukeException("\t OOPS!!! The description of a event cannot be empty.\n"); + } + String[] startEndTime = inputLine[1].split(" /from "); + if (startEndTime.length < 2) { + throw new DukeException("\t OOPS!!! The start time of a event cannot be empty.\n"); + } + String[] dateTime = startEndTime[1].split(" /to "); + if (dateTime.length < 2) { + throw new DukeException("\t OOPS!!! The end time of a event cannot be empty.\n"); + } + return new Event(startEndTime[0], false, + DateTime.dateFormatter(dateTime[0]), DateTime.dateFormatter(dateTime[1])); + } + + /** + * Returns a event task from the file. + * + * @param taskLine Each line from the input file. + * @return Event task. + */ + public static Event generateTask(String[] taskLine) { + boolean isDone = taskLine[1].equals("1"); + return new Event(taskLine[2], isDone, taskLine[3], taskLine[4]); + } + public String getStart() { + return start; + } + public String getEnd() { + return end; + } + @Override + public String getTaskType() { + return "E"; + } + @Override + public String storeTaskString() { + String str = this.getTaskType() + " | " + + this.getMarkedString() + " | " + + this.getDescription() + " | " + + this.getStart() + " | " + this.getEnd(); + return str; + } + + @Override + public String toString() { + String discription = this.getDescription(); + boolean checked = this.isDone(); + String startTime = this.getStart(); + String endTime = this.getEnd(); + String str = ""; + if (checked) { + str = "[E][X] " + discription + " (from: " + startTime + + " to: " + endTime + ")"; + } else { + str = "[E][ ] " + discription + " (from: " + startTime + + " to: " + endTime + ")"; + } + return str; + } +} diff --git a/src/main/java/task/Task.java b/src/main/java/task/Task.java new file mode 100644 index 0000000000..f508e0d263 --- /dev/null +++ b/src/main/java/task/Task.java @@ -0,0 +1,40 @@ +package task; + +/** + * Abstract class for task. + */ +public abstract class Task { + + private final String description; + + private boolean isDone; + + /** + * Class constructor. + * + * @param description The description of the task. + * @param isDone Marks or unmarks the task. + */ + public Task(String description, boolean isDone) { + this.description = description; + this.isDone = isDone; + } + + public boolean isDone() { + return isDone; + } + public String getMarkedString() { + return isDone ? "1" : "0"; + } + + public String getDescription() { + return description; + } + + public void setDone(boolean done) { + this.isDone = done; + } + + public abstract String storeTaskString(); + public abstract String getTaskType(); +} diff --git a/src/main/java/task/Todo.java b/src/main/java/task/Todo.java new file mode 100644 index 0000000000..55497da43d --- /dev/null +++ b/src/main/java/task/Todo.java @@ -0,0 +1,71 @@ +package task; + +import dukeexception.DukeException; + +/** + * Todo class to deal with todo tasks. + */ +public class Todo extends Task { + + /** + * Class constructor. + * + * @param description The description of the task. + * @param isDone Marks or unmarks the task. + */ + private Todo(String description, boolean isDone) { + super(description, isDone); + } + + + /** + * Returns todo task based on the user input. + * + * @param input User Input. + * @return Todo task. + * @throws DukeException Checks the validation of input. + */ + public static Todo generate(String input) throws DukeException { + if (input.trim().equals("todo")) { + throw new DukeException("\t OOPS!!! The description of a todo cannot be empty.\n"); + } + String[] inputLine = input.split(" ", 2); + if (inputLine.length < 2) { + throw new DukeException("\t OOPS!!! The description of a todo cannot be empty.\n"); + } + return new Todo(inputLine[1], false); + } + + /** + * Returns a todo task from the file. + * + * @param taskLine Each line from the input file. + * @return Todo task. + */ + public static Todo generateTask(String[] taskLine) { + boolean isDone = taskLine[1].equals("1"); + return new Todo(taskLine[2], isDone); + } + @Override + public String getTaskType() { + return "T"; + } + @Override + public String storeTaskString() { + String str = this.getTaskType() + " | " + + this.getMarkedString() + " | " + this.getDescription(); + return str; + } + @Override + public String toString() { + boolean checked = this.isDone(); + String discription = this.getDescription(); + String str = ""; + if (checked) { + str = "[T][X] " + discription; + } else { + str = "[T][ ] " + discription; + } + return str; + } +} diff --git a/src/main/java/userinteraction/Parser.java b/src/main/java/userinteraction/Parser.java new file mode 100644 index 0000000000..93bbd331f5 --- /dev/null +++ b/src/main/java/userinteraction/Parser.java @@ -0,0 +1,52 @@ +package userinteraction; + +import command.AddDeadlineCommand; +import command.AddEventCommand; +import command.AddTodoCommand; +import command.Command; +import command.DeleteTaskCommand; +import command.ExitCommand; +import command.FindTaskCommand; +import command.HelpCommand; +import command.InvalidCommand; +import command.ListCommand; +import command.MarkTaskCommand; + +/** + * Parses all the user input into corresponding commands to be executed. + */ +public class Parser { + + /** + * Checks whether the commands are valid. If it is valid, return a corresponding + * command. + * + * @param input The user input. + * @return The corresponding command. + */ + public static Command parse(String input) { + if (input.trim().equals("bye")) { + return new ExitCommand(input); + } else if (input.trim().equals("list")) { + return new ListCommand(input); + } else if (input.startsWith("mark")) { + return new MarkTaskCommand(input, true); + } else if (input.startsWith("unmark")) { + return new MarkTaskCommand(input, false); + } else if (input.startsWith("todo")) { + return new AddTodoCommand(input); + } else if (input.startsWith("deadline")) { + return new AddDeadlineCommand(input); + } else if (input.startsWith("event")) { + return new AddEventCommand(input); + } else if (input.startsWith("delete")) { + return new DeleteTaskCommand(input); + } else if (input.startsWith("find")) { + return new FindTaskCommand(input); + } else if (input.trim().equals("help")) { + return new HelpCommand(input); + } else { + return new InvalidCommand(input); + } + } +} diff --git a/src/main/java/userinteraction/Ui.java b/src/main/java/userinteraction/Ui.java new file mode 100644 index 0000000000..6428cba7a1 --- /dev/null +++ b/src/main/java/userinteraction/Ui.java @@ -0,0 +1,135 @@ +package userinteraction; + +import java.util.Scanner; + +import storage.Storage; +import storage.TaskList; +import task.Task; + +/** + * Ui that displays commands' message. + */ +public class Ui { + + private final Scanner scanner; + public Ui() { + scanner = new Scanner(System.in); + } + public String readCommand() { + return scanner.nextLine(); + } + + /** + * Display the welcome message. + * + * @return Returns the welcome message. + */ + public String printWelcomeMsg() { + String logo = "\t ____ _ \n" + + "\t | _ \\ _ _| | _____ \n" + + "\t | | | | | | | |/ / _ \\\n" + + "\t | |_| | |_| | < __/\n" + + "\t |____/ \\__,_|_|\\_\\___|\n"; + + String str = logo + "\t Hello! I am Duke.\n" + + "\t What can I do for you?\n"; + return str; + } + + /** + * Display the help message at start. + * + * @return Returns the get help message. + */ + public String printGetHelpMsg() { + String str = "Input 'help' to see all available commands.\n"; + return str; + } + + /** + * Display adding task message. + * + * @param taskList Stores all tasks. + * @param task The specific task needs to be displayed. + * @return The add task message being displayed. + */ + public String printAddTaskMsg(TaskList taskList, Task task) { + String str = "Got it. I've added this task:\n " + "\t " + + task.toString() + "\nNow you have " + + taskList.getSize() + " tasks in the list\n"; + return str; + } + + /** + * Display mark or unmark task message. + * + * @param isMarked Checks whether the task marked. + * @param task The specific task needs to be displayed. + * @return The mark or unmark message being displayed. + */ + public String printMarkTaskMsg(boolean isMarked, Task task) { + String str = ""; + if (isMarked) { + str = "Nice! I've marked this task as done: \n" + + "\t " + task.toString() + "\n"; + } else { + str = "OK, I've marked this task as not done yet: \n" + + "\t " + task.toString() + "\n"; + } + return str; + } + + /** + * Display delete task message. + * + * @param task The specific task needs to be displayed. + * @param size The size of the available tasks. + * @return The delete task message being displayed. + */ + public String printDeleteTaskMsg(Task task, int size) { + String str = "Noted. I've removed this task:\n" + "\t " + + task.toString() + "\nNow you have " + + size + " tasks in the list\n"; + return str; + } + + /** + * Display the find task message. + * + * @return The find task message being displayed. + */ + public String printFindTaskMsg() { + String str = "Here are the matching tasks in your list:\n"; + return str; + } + + /** + * Display the invalid command message. + * + * @return The invalid command message being displayed. + */ + public String printWrongMsg() { + String str = "OOPS!!! I'm sorry, but I don't know what that means.\n"; + return str; + } + + /** + * Display the explanation of the commands. + * + * @param storage Saves explanation in a file. + * @return The explanation of all commands being displayed. + */ + public String printHelpMsg(Storage storage) { + return storage.loadHelpExplanationFile(); + } + + /** + * Display the bye message when exit the program. + * + * @return The bye message. + */ + public String printByeMsg() { + String byeMsg = "Bye. Hope to see you again soon!"; + return byeMsg; + } +} diff --git a/src/main/java/utils/DateTime.java b/src/main/java/utils/DateTime.java new file mode 100644 index 0000000000..da492c2660 --- /dev/null +++ b/src/main/java/utils/DateTime.java @@ -0,0 +1,47 @@ +package utils; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +import dukeexception.DukeException; + +/** + * Local date time formatting. + */ +public class DateTime { + + /** + * Returns the localDateTime type of the date and time. + * + * @param date Date which is the string type. + * @return LocalDateTime type of the date and time. + * @throws DukeException Checks the validation of input. + */ + public static LocalDateTime getDateTime(String date) throws DukeException { + try { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); + LocalDateTime localDateTime = LocalDateTime.parse(date, dateTimeFormatter); + return localDateTime; + } catch (DateTimeParseException e) { + throw new DukeException("\t Wrong Date Format, please write in yyyy-MM-dd HHmm.\n"); + } + } + + /** + * Formats the date time input. + * + * @param date Date which is the string type. + * @return String type of date time. + * @throws DukeException Checks the validation of input. + */ + public static String dateFormatter(String date) throws DukeException { + try { + LocalDateTime localDateTime = getDateTime(date); + return localDateTime.format(DateTimeFormatter.ofPattern("MMM dd yyyy HH:mm")); + } catch (DateTimeParseException e) { + throw new DukeException("\t Wrong Date Format, please write in yyyy-MM-dd HHmm.\n"); + } + } + +} diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png new file mode 100644 index 0000000000..412faa3e8f Binary files /dev/null and b/src/main/resources/images/DaDuke.png differ diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png new file mode 100644 index 0000000000..ef08b4e4b7 Binary files /dev/null and b/src/main/resources/images/DaUser.png differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..80927ba52f --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..3241877641 --- /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..5446a1b019 --- /dev/null +++ b/src/test/java/task/DeadlineTest.java @@ -0,0 +1,32 @@ +package task; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import dukeexception.DukeException; + + +public class DeadlineTest { + private String input = "deadline buy book /by 2018-09-08 1800"; + + @Test + public void storeTaskString() { + try { + Deadline deadline = Deadline.generate(input); + assertEquals(deadline.storeTaskString(), "D | 0 | buy book | Sep 08 2018 18:00"); + } catch (DukeException e) { + throw new RuntimeException(e); + } + } + + @Test + public void getTaskTypeTest() { + try { + Deadline deadline = Deadline.generate(input); + assertEquals(deadline.getTaskType(), "D"); + } catch (DukeException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/utils/DateTimeTest.java b/src/test/java/utils/DateTimeTest.java new file mode 100644 index 0000000000..a474f79a29 --- /dev/null +++ b/src/test/java/utils/DateTimeTest.java @@ -0,0 +1,20 @@ +package utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import dukeexception.DukeException; + + +public class DateTimeTest { + @Test + public void getDateTimeTest() { + try { + assertEquals(DateTime.dateFormatter("2018-08-05 1800"), "Aug 05 2018 18:00"); + assertEquals(DateTime.dateFormatter("2022-02-02 0937"), "Feb 02 2022 09:37"); + } catch (DukeException e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e7..444e8ed366 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,56 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| + ____ _ + | _ \ _ _| | _____ + | | | | | | | |/ / _ \ + | |_| | |_| | < __/ + |____/ \__,_|_|\_\___| + Hello! I'm Duke + What can I do for you? + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Got it. I've added this task: + [T] [ ] borrow book + Now you have 1 tasks in the list. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Got it. I've added this task: + [D] [ ] return book (by: Sunday) + Now you have 2 tasks in the list. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + 1.[T] [ ] borrow book + 2.[D] [ ] return book (by: Sunday) + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Nice! I've marked this task as done: + [X] return book + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Got it. I've added this task: + [E] [ ] project meeting (from: Mon 2pm to: 4pm) + Now you have 3 tasks in the list. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Nice! I've marked this task as done: + [X] project meeting + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + OK, I've marked this task as not done yet: + [ ] return book + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + 1.[T] [ ] borrow book + 2.[D] [ ] return book (by: Sunday) + 3.[E] [X] project meeting (from: Mon 2pm to: 4pm) + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Bye. Hope to see you again soon! \ No newline at end of file diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb2..c0eba47e75 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,9 @@ +todo borrow book +deadline return book /by Sunday +list +mark 2 +event project meeting /from Mon 2pm /to 4pm +mark 3 +unmark 2 +list +bye diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 0873744649..1873b3db23 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -8,6 +8,8 @@ if exist ACTUAL.TXT del ACTUAL.TXT REM compile the code into the bin folder javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\*.java + + IF ERRORLEVEL 1 ( echo ********** BUILD FAILURE ********** exit /b 1 diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh old mode 100644 new mode 100755