diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000000..c6b7ee2b1e --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,50 @@ +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 + + - name: Perform IO redirection test (*NIX) + if: runner.os == 'Linux' + working-directory: ${{ github.workspace }}/text-ui-test + run: ./runtest.sh + + - name: Perform IO redirection test (MacOS) + if: always() && runner.os == 'macOS' + working-directory: ${{ github.workspace }}/text-ui-test + run: ./runtest.sh + + - name: Perform IO redirection test (Windows) + if: always() && runner.os == 'Windows' + working-directory: ${{ github.workspace }}/text-ui-test + shell: cmd + run: runtest.bat diff --git a/README.md b/README.md index 9d95025bce..d0047b4e52 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,34 @@ -# Duke project template +# Duke project +Duke Application, made by [Stephen Tan](https://www.linkedin.com/in/stephen-tan-hin-khai/) under +the module CS2103T 2020/21 Sem 1. +This is a greenfield Java project. It's named after the Java mascot _Duke_. -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. +Please check [here](docs/README.md) for the User Guide -## Setting up in Intellij +## Pre-Requisites for use +Install Java 11 +## Getting Started +To execute the program, or start the Duke Chatbot: -Prerequisites: JDK 11, update Intellij to the most recent version. +Double click the jar file, or enter by command prompt +``` +java -jar duke.jar +``` +If you prefer a Command Line Interface, type: +``` +java -jar duke.jar -cli +``` +Then, type the help command to get help on what Duke can do. +``` +help +``` -1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project dialog first) -1. Set up the correct JDK version, as follows: - 1. Click `Configure` > `Structure for New Projects` and then `Project Settings` > `Project` > `Project SDK` - 1. If JDK 11 is listed in the drop down, select it. If it is not, click `New...` and select the directory where you installed JDK 11 - 1. Click `OK` -1. Import the project into Intellij as follows: - 1. Click `Open or Import`. - 1. Select the project directory, and click `OK` - 1. If there are any further prompts, accept the defaults. -1. After the importing is complete, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below: - ``` - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - ``` +![Duke UI](docs/Ui.png) + +## Acknowledgements +In this project, I acknowledge referencing [here](https://github.com/JoeyChenSmart/ip) +for inspiration for the functional software pattern used. + +Use of Gradle project Wrapper. + +Initial Template is taken from [Prof Damith](https://github.com/damithc) \ No newline at end of file diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000000..9da9a0291e --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-dinky \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..b80ce25da9 --- /dev/null +++ b/build.gradle @@ -0,0 +1,60 @@ +plugins { + id 'java' + id 'application' + id 'checkstyle' + id 'com.github.johnrengelman.shadow' version '5.1.0' +} + +repositories { + mavenCentral() +} + +dependencies { + String javaFxVersion = '11.0.1' + 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' + 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' +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + +application { + mainClassName = "duke.ChatbotApplication" +} + +shadowJar { + archiveBaseName = "duke" + archiveClassifier = null +} + +checkstyle { + toolVersion = '8.29' +} + +run{ + enableAssertions(true) + standardInput = System.in +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..bb15f3a86b --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,403 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..39efb6e4ac --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/config/travis/check-eof-newline.sh b/config/travis/check-eof-newline.sh new file mode 100755 index 0000000000..b771f3988d --- /dev/null +++ b/config/travis/check-eof-newline.sh @@ -0,0 +1,18 @@ +#!/bin/sh +# Checks that all text files end with a newline. + +ret=0 + +# Preserve filename with spaces by only splitting on newlines. +IFS=' +' + +for filename in $(git grep --cached -I -l -e '' -- ':/'); do + if [ "$(tail -c 1 "./$filename")" != '' ]; then + line="$(wc -l "./$filename" | cut -d' ' -f1)" + echo "ERROR:$filename:$line: no newline at EOF." + ret=1 + fi +done + +exit $ret diff --git a/config/travis/check-line-endings.sh b/config/travis/check-line-endings.sh new file mode 100755 index 0000000000..3de67ea87f --- /dev/null +++ b/config/travis/check-line-endings.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# Checks for prohibited line endings. +# Prohibited line endings: \r\n + +git grep --cached -I -n --no-color -P '\r$' -- ':/' | +awk ' + BEGIN { + FS = ":" + OFS = ":" + ret = 0 + } + { + ret = 1 + print "ERROR", $1, $2, " prohibited \\r\\n line ending, use \\n instead." + } + END { + exit ret + } +' diff --git a/config/travis/check-trailing-whitespace.sh b/config/travis/check-trailing-whitespace.sh new file mode 100755 index 0000000000..33841caa81 --- /dev/null +++ b/config/travis/check-trailing-whitespace.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# Checks for trailing whitespace + +git grep --cached -I -n --no-color -P '[ \t]+$' -- ':/' | +awk ' + BEGIN { + FS = ":" + OFS = ":" + ret = 0 + } + { + # Only warn for markdown files (*.md) to accomodate text editors + # which do not properly handle trailing whitespace. + # (e.g. GitHub web editor) + if ($1 ~ /\.md$/) { + severity = "WARN" + } else { + severity = "ERROR" + ret = 1 + } + print severity, $1, $2, " trailing whitespace." + } + END { + exit ret + } +' diff --git a/config/travis/run-checks.sh b/config/travis/run-checks.sh new file mode 100755 index 0000000000..7aad1e9622 --- /dev/null +++ b/config/travis/run-checks.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# Runs all check-* scripts, and returns a non-zero exit code if any of them fail. + +dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) && +ret=0 && +for checkscript in "$dir"/check-*; do + if ! "$checkscript"; then + ret=1 + fi +done +exit $ret diff --git a/docs/README.md b/docs/README.md index fd44069597..69a0847fba 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,20 +1,136 @@ # User Guide +![Duke UI](Ui.png) +This is a user guide for the usage of Duke Application, made by +[Stephen Tan](https://www.linkedin.com/in/stephen-tan-hin-khai/) under +the module CS2103T 2020/21 Sem 1. -## Features +Duke Chatbot 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. -### Feature 1 -Description of feature. +## Pre-Requisites for use +Install Java 11 +## Getting Started +To execute the program, or start the Duke Chatbot: +Double click the jar file, or enter by command prompt +``` +java -jar duke.jar +``` +If you prefer a Command Line Interface, type: +``` +java -jar duke.jar -cli +``` +Then, type the help command to get help on what Duke can do. +``` +help +``` -## Usage +## Features supported +Scope of features supported. +1. Create new tasks +2. View a list of tasks +3. Use regex to find tasks by matching substring +4. Get help about commands from Duke +5. Update a task as done +6. Delete tasks by given index +7. GUI or CLI interface +8. User Feedback for bad input +9. Saves and loads tasks upon execution of program -### `Keyword` - Describe action +## Command Usage -Describe action and its outcome. - -Example of usage: - -`keyword (optional arguments)` - -Expected outcome: - -`outcome` +### Adding and Scheduling Tasks: +You can schedule tasks on the chatbot. +There are up to 3 kinds of tasks supported. +#### 1. Todo tasks +##### command: +``` +todo +``` +Todo tasks have no deadline and thus have no requirements on completion +##### Expected outcome: +In the system will register a `todo task` +##### Upon wrong input: +Duke will indicate a bad command input was given and return a specific +error relating to the bad input +#### 2. Deadline Tasks +##### command: +``` +deadline -by dd-MM-YYYY +``` +Deadline tasks have a deadline and will indicate how close the given date is +from the deadline using the current time. For deadline tasks, there is limited +level of autocorrection Hence you can just specify a day > the current day's date +Eg: if date is 12 September 2020 +``` +deadline example task -by 13 +``` +##### Expected outcome: +In the system will register a `deadline due by 14-9-2020` +##### Upon bad input +Duke will indicate that datetime is invalid, or an invalid input was given +to the description field +#### 3. Event Tasks +##### command: +``` +event -at dd-MM-YYYY +``` +Event tasks have a date of event and will indicate how close the given date is from the +event using the current time. For event tasks, there is limited level of autocorrection +Hence you can just specify a day > the current day's date +Eg: if date is 12 September 2020 +``` +event example task -by 13 +``` +##### Expected outcome: +In the system will register a `event due by 14-9-2020` +##### Upon bad input +Duke will indicate that datetime is invalid, or an invalid input was given +to the description field +### Listing Tasks: +You can list all tasks registered in the system with the command +``` +list +``` +### Search for Task: +You can find all tasks registered in the system by description using a + substring or a regex expression. +``` +find +``` +### Mark task done: +You can mark a task done by using the index of the task referenced from before. +``` +done +``` +##### Expected outcome: +Duke will mark the task as done +##### Upon bad input +Duke will indicate that the input is either out-of-index or not a number. +### Delete Task +You can delete a task by using the index of the task referenced from before. +``` +delete +``` +##### Expected outcome: +Duke will delete the task +##### Upon bad input +Duke will indicate that the input is either out-of-index or not a number. +### Help +You can get help about Duke by issuing the command +``` +help +``` +You can also get specific help about certain commands through adding the +specific command itself. +``` +help +``` +##### Expected outcome: +Duke will respond with a help message. +##### Upon wrong input: +Duke will respond with the all help Duke message. +### End Application +You can end the execution of the application by issuing the command `bye` +``` +bye +``` \ No newline at end of file diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..3b3b13fdff Binary files /dev/null and b/docs/Ui.png differ diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000000..9da9a0291e --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-dinky \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..f3d88b1c2f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..b7c8c5dbf5 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..2fe81a7d95 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..62bd9b9cce --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,103 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334cc..0000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..68dde2fd46 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: duke.ChatbotApplication + diff --git a/src/main/java/duke/ChatbotApplication.java b/src/main/java/duke/ChatbotApplication.java new file mode 100644 index 0000000000..3d1a001706 --- /dev/null +++ b/src/main/java/duke/ChatbotApplication.java @@ -0,0 +1,38 @@ +package duke; + +import duke.ui.CommandLineInterface; +import duke.ui.MainLauncher; +import duke.ui.UserInterface; +import javafx.application.Application; + + +/** + * Front End Facing Script for the UI of duke.ChatbotApplication + */ +public class ChatbotApplication { + private final Duke dukeProgram; + /** + * Constructor class of the duke.ChatbotApplication + * @param ui the path to read a file from. + */ + public ChatbotApplication(UserInterface ui) { + dukeProgram = new Duke(ui); + } + + /** + * Entry point for application + * @param args args + */ + public static void main(String[] args) { + //args[0] = "cli"; + if (args.length > 0 && args[0].equals("-cli")) { + UserInterface ui = new CommandLineInterface(); + Duke duke = new Duke(ui); + while (ui.isRunning()) { + duke.nextIteration(); + } + } else { + Application.launch(MainLauncher.class, args); + } + } +} diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java new file mode 100644 index 0000000000..51b6004f79 --- /dev/null +++ b/src/main/java/duke/Duke.java @@ -0,0 +1,82 @@ +package duke; + +import java.util.Optional; +import java.util.regex.Matcher; + +import duke.command.CommandEnums; +import duke.exceptions.DukeCommandException; +import duke.exceptions.DukeException; +import duke.exceptions.DukeIoException; +import duke.tasks.TaskManager; +import duke.tasks.TextParser; +import duke.ui.UserInterface; + + + +/** + * Backend Object Class for the duke.Duke Chatbot Interface + */ +public class Duke { + private static final TextParser textParser = new TextParser(); + private final TaskManager taskManager; + private final UserInterface ui; + /** + * Constructor for the duke.Duke Chatbot, if is old initialisation, will read from txt file + * Eles it will initialise a new TaskManager class + * @param ui The user interface to use. + */ + + public Duke(UserInterface ui) { + assert ui != null : "ui will never be null"; + TaskManager list1; + try { + list1 = new TaskManager(System.getProperty("user.dir")); + } catch (DukeIoException e) { + list1 = new TaskManager(System.getProperty("user.dir"), true); + } + taskManager = list1; + this.ui = ui; + this.ui.start("Friend"); + } + private void parseRun(String input) throws DukeException { + for (CommandEnums cmd : CommandEnums.values()) { + Optional maybeMatcher = cmd.matcher(input); + if (maybeMatcher.isEmpty()) { + continue; + } + Matcher match = maybeMatcher.get(); + if (!match.find()) { + throw cmd.commandError(input); + } + int count = match.groupCount(); + String[] arguments = new String[count]; + for (int i = 1; i <= count; i++) { + arguments[i - 1] = match.group(i); + } + cmd.execute(taskManager, ui, arguments); + return; + } + throw new DukeCommandException(input); + } + + /** + * Runs the Next Iteration of Duke + * To be invoked to iterate the continuation of Duke Application flow. + */ + public void nextIteration() { + String input = textParser.cleanInput(ui.nextLine()); + try { + parseRun(input); + } catch (DukeException e) { + ui.systemMessage(e.toString()); + } + } + + @Override + public String toString() { + return "Duke{" + + "taskManager=" + taskManager + + ", ui=" + ui + + '}'; + } +} diff --git a/src/main/java/duke/command/CommandEnums.java b/src/main/java/duke/command/CommandEnums.java new file mode 100644 index 0000000000..471f62b8dc --- /dev/null +++ b/src/main/java/duke/command/CommandEnums.java @@ -0,0 +1,72 @@ +package duke.command; + +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import duke.exceptions.DukeCommandException; +import duke.exceptions.DukeException; +import duke.tasks.TaskManager; +import duke.ui.UserInterface; + +public enum CommandEnums { + TODO(CommandList.TODO , "(.*)" , "todo"), + DEADLINE(CommandList.DEADLINE, "(.*) -by (.*)", "deadline"), + EVENT(CommandList.EVENT , "(.*) -at (.*)" , "event"), + HELP(CommandList.HELP, "(.*)", "help"), + LIST(CommandList.LIST, "" , "list"), + FIND(CommandList.FIND, "(.*)" , "find"), + DELETE(CommandList.DELETE , "(\\d+)" , "delete"), + DONE(CommandList.DONE, "(\\d+)" , "done"), + BYE(CommandList.BYE , "" , "bye"); + private final CommandExecuter executer; + private final Pattern format; + private final String name; + + /** + * Constructs a CommandEnums which contains the static functional interface for interacting in application + * @param executer Functional interface for a specific command + * @param formatString Regex format for a command + * @param name name of command to match. + */ + CommandEnums(CommandExecuter executer, String formatString, String name) { + this.executer = executer; + format = Pattern.compile(formatString); + this.name = name; + } + + /** + * Parse string output to return a optional.of(regex.Matcher) if there is a match + * Otherwise, returns Optional.empty(). Matcher is configured to return the arguments to the Command + * in its Capture Groups. + * @param rawInput The user input + * @return Optional regex Match if exists, else empty + */ + public Optional matcher(String rawInput) { + if (!rawInput.startsWith(name)) { + return Optional.empty(); + } + Matcher matcher = format.matcher(rawInput.substring(name.length()).trim()); + return Optional.of(matcher); + } + + /** + * Executes the task using the taskmanager and user interface + * @param taskManager stored taskManager in parser + * @param ui User Interface + * @param args String argument + * @throws DukeException an Exception in the internal commandlist + */ + public void execute(TaskManager taskManager, UserInterface ui, String[] args) throws DukeException { + executer.run(taskManager, ui , args); + } + + /** + * Input given that is known to be wrong + * @param input userinput + * @return Error Message + */ + public DukeCommandException commandError(String input) { + return new DukeCommandException(input); + } +} diff --git a/src/main/java/duke/command/CommandExecuter.java b/src/main/java/duke/command/CommandExecuter.java new file mode 100644 index 0000000000..7d04591abe --- /dev/null +++ b/src/main/java/duke/command/CommandExecuter.java @@ -0,0 +1,10 @@ +package duke.command; + +import duke.exceptions.DukeException; +import duke.tasks.TaskManager; +import duke.ui.UserInterface; + +@FunctionalInterface +interface CommandExecuter { + void run(TaskManager taskManager, UserInterface ui, String[] arguments) throws DukeException; +} diff --git a/src/main/java/duke/command/CommandHelp.java b/src/main/java/duke/command/CommandHelp.java new file mode 100644 index 0000000000..612cabd5a8 --- /dev/null +++ b/src/main/java/duke/command/CommandHelp.java @@ -0,0 +1,52 @@ +package duke.command; + +/** + * Command is a Storage for the Enumerations of each type of command that is valid in Duke Chatbot. + */ +public enum CommandHelp { + //random string as this is the default enum. + ERROR("asjdbaksjfbanfjknjkdfnskasd", "This is a invalid command"), + BYE("bye", "close the application"), + HELP("help", "Get help for a specific command via help -cmd command"), + LIST("list" , "list the current list of Tasks and their statuses"), + DONE("done" , "set a task as done via index: done 1 to mark Task 1 as done"), + DELETE("delete", "delete a task via index: delete 1 to delete first task"), + TODO("todo", "schedule a untimed task"), + DEADLINE("deadline", "schedule a timed deadline task, please structure with " + + "[deadline -by dd-MM-YYYY]"), + EVENT("event", "schedule a timed event task, please structure with " + + "[event -at dd-MM-YYYY]"), + SEARCH("find", "find on description only"), + BLANK("", "This is a invalid command"); + private final String commandEncoding; + private final String helpMsg; + + /** + * Constructs the enumeration for help commands + * @param code key word of command + * @param help Help message to display + */ + CommandHelp(String code, String help) { + commandEncoding = code; + helpMsg = help; + } + /** + * Getter for command that is encoded in a Command + * @return exact text that represents a command in Duke + */ + public String getCode() { + return commandEncoding; + } + public String toString() { + return "- " + commandEncoding + " to " + helpMsg + "\n"; + } + + /** + * Checks for equivilence between the code of the command and the other + * @param other Other command + * @return boolean indicating that the command is the same. + */ + public boolean equals(CommandHelp other) { + return other.getCode().equals(getCode()); + } +} diff --git a/src/main/java/duke/command/CommandList.java b/src/main/java/duke/command/CommandList.java new file mode 100644 index 0000000000..f90ce41031 --- /dev/null +++ b/src/main/java/duke/command/CommandList.java @@ -0,0 +1,78 @@ +package duke.command; + +import duke.exceptions.DukeNoInputException; +import duke.tasks.Deadline; +import duke.tasks.Event; +import duke.tasks.TextParser; +import duke.tasks.ToDo; + + + +/** + * CommandList to store commands supported by duke + * Software pattern referenced from https://github.com/JoeyChenSmart/ip + * Implementation is done by myself with reference to this example + */ +class CommandList { + private static final String ALLHELP = "\t Need some help huh?\n" + + "Heres a list of my commands!\n" + + "- 'bye' to close the application\n" + + "- 'list' to list the current list of duke.tasks and their statuses\n" + + "- 'done' to set a task as done\n" + + "- 'find' to find a task using regex or a query text string\n" + + "- 'todo' to list a untimed task\n" + + "- 'deadline' to list a timed deadline task, please structure with " + + "[deadline -by dd-MM-YYYY]\n" + + "- 'event' to list a timed event task, please structure with " + + "[event -at dd-MM-YYYY]\n" + + "- 'help' to list these commands again help for specific commands\n"; + private static final TextParser PARSER = new TextParser(); + static final CommandExecuter DEADLINE = (taskManager, ui, arguments) -> { + if (arguments.length == 0) { + throw new DukeNoInputException(); + } + assert arguments.length == 2 : "The length of the argument should always only be 2" + + " because of -by seperator"; + ui.systemMessage(taskManager.add(Deadline.createNewDeadline(arguments[0] , arguments[1]))); + }; + static final CommandExecuter TODO = (taskManager, ui, arguments) -> { + if (arguments[0].isEmpty()) { + throw new DukeNoInputException(); + } + assert arguments.length == 1 : "The length of the argument should always only be 1"; + ui.systemMessage(taskManager.add(new ToDo(arguments[0]))); + }; + static final CommandExecuter EVENT = (taskManager, ui, arguments) -> { + if (arguments.length == 0) { + throw new DukeNoInputException(); + } + assert arguments.length == 2 : "The length of the argument should always only be 2" + + " because of -at seperator"; + ui.systemMessage(taskManager.add(Event.createNewEvent(arguments[0], arguments[1]))); + }; + static final CommandExecuter FIND = (taskManager, ui, arguments) -> { + ui.systemMessage(taskManager.findTasks(arguments[0])); + }; + static final CommandExecuter BYE = (taskManager, ui, arguments) -> { + taskManager.saveTasks(); + ui.close(); + }; + static final CommandExecuter DELETE = (taskManager, ui, arguments) -> { + System.out.print(arguments[0]); + ui.systemMessage(taskManager.deleteTask(arguments[0])); + }; + static final CommandExecuter DONE = (taskManager, ui, arguments) -> { + ui.systemMessage(taskManager.doTask(arguments[0])); + }; + static final CommandExecuter LIST = (taskManager, ui, arguments) -> { + ui.systemMessage(taskManager.listTasks()); + }; + static final CommandExecuter HELP = (taskManager, ui, arguments) -> { + CommandHelp commandHelp = PARSER.parseHelpCommand(arguments[0]); + String message = commandHelp.toString(); + if (commandHelp.equals(CommandHelp.ERROR) || commandHelp.equals(CommandHelp.BLANK)) { + message = ALLHELP; + } + ui.systemMessage(message); + }; +} diff --git a/src/main/java/duke/exceptions/DukeBlankCommandException.java b/src/main/java/duke/exceptions/DukeBlankCommandException.java new file mode 100644 index 0000000000..f12c56d1a3 --- /dev/null +++ b/src/main/java/duke/exceptions/DukeBlankCommandException.java @@ -0,0 +1,14 @@ +package duke.exceptions; + +/** + * Error Type of a Blank Command in Duke Application. + */ +public class DukeBlankCommandException extends DukeException { + /** + * Constructor for DukeBlankCommandException for a blank command + * @param s String form of the bad input + */ + public DukeBlankCommandException(String s) { + super(s, 4); + } +} diff --git a/src/main/java/duke/exceptions/DukeCommandException.java b/src/main/java/duke/exceptions/DukeCommandException.java new file mode 100644 index 0000000000..4c8d9fa250 --- /dev/null +++ b/src/main/java/duke/exceptions/DukeCommandException.java @@ -0,0 +1,14 @@ +package duke.exceptions; + +/** + * Error Type of a Invalid Command in Duke Application. + */ +public class DukeCommandException extends DukeException { + /** + * Constructor class for DukeCommandException + * @param badCommand the command that is unrecognisable by Duke Application + */ + public DukeCommandException(String badCommand) { + super(badCommand , 1); + } +} diff --git a/src/main/java/duke/exceptions/DukeDateTimeException.java b/src/main/java/duke/exceptions/DukeDateTimeException.java new file mode 100644 index 0000000000..e46b7793dd --- /dev/null +++ b/src/main/java/duke/exceptions/DukeDateTimeException.java @@ -0,0 +1,29 @@ +package duke.exceptions; + +/** + * Error Type of a DateTimeError in Duke Application. + * This error is thrown if the DateTime does not match or is incompatible with the DateTimeFormatter + */ +public class DukeDateTimeException extends DukeException { + /** + * Constructor class for DukeDateTimeException + * @param cmd the invalid command + */ + public DukeDateTimeException(String cmd) { + super(cmd, 3); + } + + /** + * Takes in the given bad input and the code + * @return String + */ + public String message(String s) { + StringBuilder b = new StringBuilder(); + b.append("Oops you did not mark your datetime! Not sure what you mean by:\n"); + b.append(badCommand).append("\n"); + b.append(s); + b.append(": ").append(code.toString()).append("\n"); + b.append("Heres a tip, use the 'help' command to learn about my commands!\n"); + return b.toString(); + } +} diff --git a/src/main/java/duke/exceptions/DukeException.java b/src/main/java/duke/exceptions/DukeException.java new file mode 100644 index 0000000000..4ba954d555 --- /dev/null +++ b/src/main/java/duke/exceptions/DukeException.java @@ -0,0 +1,49 @@ +package duke.exceptions; + +/** + * DukeException is a classification of errors that pertain to any running problems within Duke Class applications + * Some errors that may occur in the hierarchy of data flow: + * 1. FileRead Error (WIP): For handling stored memory and I/O errors + * 2. Bad Command Given: When a Command that is unknown is given + * 3. No Input given: When in the flow for a given command, no description is detected + * 4. Bad Date Given: For handling datetime parsing errors. + * 5. Blank Command Given: For handling when a Blank command is given to a input. + * 6. Index Error: When a invalid index is given + * 7. UnknownException: For handling anything exceptionally unexpected + */ +public abstract class DukeException extends Exception { + + protected final String badCommand; + protected final ErrorEncode code; + + /** + * Constructor class for a Generic DukeException for any errortype encountered in Duke + * @param badCommand The command or user input that is causing the error + * @param code The enumeration to encode the error message. + */ + protected DukeException(String badCommand, int code) { + this.badCommand = badCommand; + this.code = ErrorEncode.parseCode(code); + } + + /** + * Returns the template user error message of the DukeException class + * @param s String Message to wrap generic error Message + * @return String message to be printed out to player. + */ + public String message(String s) { + StringBuilder b = new StringBuilder(); + b.append("Oops you used a invalid command! Not sure what you mean by:\n"); + b.append(badCommand).append("\n"); + b.append(s); + b.append(": ").append(code.toString()).append("\n"); + b.append("Heres a tip, use the 'help' command to learn about my commands!\n"); + return b.toString(); + } + + @Override + public String toString() { + + return message(super.toString()); + } +} diff --git a/src/main/java/duke/exceptions/DukeIndexException.java b/src/main/java/duke/exceptions/DukeIndexException.java new file mode 100644 index 0000000000..22d1bed7a7 --- /dev/null +++ b/src/main/java/duke/exceptions/DukeIndexException.java @@ -0,0 +1,31 @@ +package duke.exceptions; + +/** + * Error type for Index Errors that appear when trying to perform a command. + */ +public class DukeIndexException extends DukeException { + private int size; + /** + * Constructs a DukeIndexException to indicate that the given command or index is invalid + * @param cmd Command or Index that is invalid + * @param size The actual or given size of the list + */ + public DukeIndexException(String cmd, int size) { + super(cmd, 5); + this.size = size; + } + + /** + * Takes in the given bad input and the code + * @return String message + */ + public String message(String s) { + StringBuilder b = new StringBuilder(""); + b.append("Oops you requested for a index ourside the list range or a non numeric index:\n"); + b.append(badCommand).append("\n"); + b.append(s); + b.append(": ").append(code.toString()).append(" out of ").append(size).append("\n"); + b.append("Heres a tip, use the 'list' command to see the current duke.tasks!\n"); + return b.toString(); + } +} diff --git a/src/main/java/duke/exceptions/DukeIoException.java b/src/main/java/duke/exceptions/DukeIoException.java new file mode 100644 index 0000000000..3cc451acd4 --- /dev/null +++ b/src/main/java/duke/exceptions/DukeIoException.java @@ -0,0 +1,14 @@ +package duke.exceptions; + +/** + * Error type for I/O that appear when trying to perform a read or write command. + */ +public class DukeIoException extends DukeException { + /** + * Constructor for I/O exception class. + * @param badCommand the part of the I/O process that is causing an error + */ + public DukeIoException(String badCommand) { + super(badCommand, 0); + } +} diff --git a/src/main/java/duke/exceptions/DukeNoInputException.java b/src/main/java/duke/exceptions/DukeNoInputException.java new file mode 100644 index 0000000000..072fa1218f --- /dev/null +++ b/src/main/java/duke/exceptions/DukeNoInputException.java @@ -0,0 +1,14 @@ +package duke.exceptions; + +/** + * Error Type of a Invalid Description for a task, or if it is not given. + */ +public class DukeNoInputException extends DukeException { + /** + * Constructs class for a DukeNoInputException + * No input is given to this command hence returning the no input exception + */ + public DukeNoInputException() { + super("", 2); + } +} diff --git a/src/main/java/duke/exceptions/DukeUnknownException.java b/src/main/java/duke/exceptions/DukeUnknownException.java new file mode 100644 index 0000000000..7b7691e618 --- /dev/null +++ b/src/main/java/duke/exceptions/DukeUnknownException.java @@ -0,0 +1,14 @@ +package duke.exceptions; + +/** + * Exception Class to encapsulate any exceptional error that does not fit any of the previous criterion + */ +public class DukeUnknownException extends DukeException { + /** + * Constructor class for the DukeUnknownException + * @param message message or input that caused this unexpected error. + */ + public DukeUnknownException(String message) { + super(message, 99); + } +} diff --git a/src/main/java/duke/exceptions/ErrorEncode.java b/src/main/java/duke/exceptions/ErrorEncode.java new file mode 100644 index 0000000000..d8e8f8872d --- /dev/null +++ b/src/main/java/duke/exceptions/ErrorEncode.java @@ -0,0 +1,51 @@ +package duke.exceptions; +/** + * Enum to encapsulate all generic messages for specific error types + */ +public enum ErrorEncode { + FileRead(0, "A I/O and Filereading Error has occured."), + BadCommandGiven(1, "I cannot understand what that command means."), + NoInputGiven(2, "There was no Input given for the task."), + BadDateGiven(3, "You did not give a valid date!"), + BlankCommand(4, "No input command was given"), + IndexError(5, "The index of the task is out of bounds"), + UnknownError(99, "Something Exceptionally unexpected has happened!" + + " We will shut the application down"); + + private final int code; + private final String description; + + ErrorEncode(int code, String description) { + this.code = code; + this.description = description; + } + + /** + * Parse an error code into the Enum List for the given error message + * @param code int code of error + * @return ErrorEncode Enumeration of the type of code + */ + public static ErrorEncode parseCode(int code) { + ErrorEncode e = UnknownError; + for (ErrorEncode i : ErrorEncode.values()) { + if (i.getCode() == code) { + e = i; + break; + } + } + return e; + } + + public String getDescription() { + return description; + } + + public int getCode() { + return code; + } + + @Override + public String toString() { + return getDescription(); + } +} diff --git a/src/main/java/duke/tasks/Deadline.java b/src/main/java/duke/tasks/Deadline.java new file mode 100644 index 0000000000..d08cda2665 --- /dev/null +++ b/src/main/java/duke/tasks/Deadline.java @@ -0,0 +1,75 @@ +package duke.tasks; + + +import duke.exceptions.DukeDateTimeException; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; + +/** + * The Event class contains the information that is entered by the user into the + * Duke Chatbot Command Line Interface. + */ +public class Deadline extends TimedTask { + /** + * Constructor for Deadline Class without done status + * To be used for creating new duke.tasks by the end user + * @param desc the description of the task + * @param date the date on which the task is due + * @throws DukeDateTimeException if the fields for the date are not matching autocorrection cases + */ + private Deadline(String desc, String date) throws DukeDateTimeException { + super(desc, date); + } + /** + * Constructor for Deadline class with done status + * To be used by the I/O manager to read duke.tasks and populate the system at runtime. + * @param desc the description of the task. + * @param date the date on which the task is due. + * @param b the Done Status of the Task. + * @throws DukeDateTimeException if the fields for the date are not matching autocorrection cases + */ + Deadline(String desc , String date , Boolean b) throws DukeDateTimeException { + super(desc, date, b); + } + /** + * Static Factory method Creates new Deadlines from user input, and adds additional constraint on user + * input in order to input a valid new event + * @param desc Deadline description + * @param date date or partial date for + * @return Deadline created from a validated input date. + * @throws DukeDateTimeException if the date given by user is before, or is autocorrected before date + */ + public static Deadline createNewDeadline(String desc, String date) throws DukeDateTimeException { + try { + if (NOW.format(FMAT).length() > date.length()) { + date = date + NOW.format(FMAT).substring(date.length()); + } + LocalDateTime dateby = LocalDateTime.parse(date, FMAT); + if (dateby.toLocalDate().isBefore(NOW)){ + throw new DukeDateTimeException("The date you entered "+ dateby.toLocalDate() + " is before today:" + + NOW + " or is not in the correct format!"); + } + return new Deadline(desc, date); + } catch (DateTimeParseException e) { + throw new DukeDateTimeException("The String you entered does not meet the " + + "required format of 'yyyy-MM-dd' "); + } + + + } + @Override + public String toString() { + return "[D]" + super.toString() + " (by: " + getDateby() + ") You have " + + (timeLeft() > -1 ? timeLeft() + " days left till its due!" + : "this task due since " + timeLeft() * -1 + " days ago!"); + } + /** + * Returns a String Representation of the Deadline object class to write to text file. + * @return the saved task to write to a text file + */ + @Override + public String saveTask() { + return "D" + SEPERATOR + super.saveTask(); + } +} diff --git a/src/main/java/duke/tasks/Event.java b/src/main/java/duke/tasks/Event.java new file mode 100644 index 0000000000..7db05ae990 --- /dev/null +++ b/src/main/java/duke/tasks/Event.java @@ -0,0 +1,76 @@ +package duke.tasks; + +import duke.exceptions.DukeDateTimeException; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; + +/** + * The Event class contains the information that is entered by the user into the + * Duke Chatbot Command Line Interface. + */ +public class Event extends TimedTask { + /** + * Constructs Event Class without done status + * To be used for creating new Tasks by the end user + * @param desc the description of the task + * @param date the date on which the task is due + * @throws DukeDateTimeException Throws Exception if the given date is parsed and returns invalid + */ + private Event(String desc, String date) throws DukeDateTimeException { + super(desc, date); + } + + /** + * Constructs Event class with done status + * To be used by the I/O manager to read duke.tasks and populate the system at runtime + * @param desc the description of the task + * @param date the date on which the task is due + * @param b the Done Status of the Task + * @throws DukeDateTimeException if the fields for the date are not matching autocorrection cases + */ + Event(String desc, String date, Boolean b) throws DukeDateTimeException { + super(desc, date, b); + } + + @Override + public String toString() { + return "[E]" + super.toString() + " (at: " + getDateby() + ") You have " + + (timeLeft() > -1 ? timeLeft() + " days left till the event!" + : "had this event " + timeLeft() * -1 + " days ago!"); + } + + /** + * Static Factory method Creates new events from user input, and adds additional constraint on user + * input in order to input a valid new event + * @param desc Event description + * @param date date or partial date for + * @return Event created from a validated input date. + * @throws DukeDateTimeException if the date given by user is before, or is autocorrected before date + */ + public static Event createNewEvent(String desc, String date) throws DukeDateTimeException { + try { + if (NOW.format(FMAT).length() > date.length()) { + date = date + NOW.format(FMAT).substring(date.length()); + } + LocalDateTime dateby = LocalDateTime.parse(date, FMAT); + if (dateby.toLocalDate().isBefore(NOW)){ + throw new DukeDateTimeException("The date you entered " + dateby.toLocalDate() + "is before today:" + + NOW + " or is " + "not in the correct format!"); + } + return new Event(desc, date); + } catch (DateTimeParseException e) { + throw new DukeDateTimeException("The String you entered does not meet the " + + "required format of 'yyyy-MM-dd' "); + } + + } + /** + * Returns a String Representation of the Event object class to write to text file. + * @return the saved task to write to a text file + */ + @Override + public String saveTask() { + return "E" + SEPERATOR + super.saveTask(); + } +} diff --git a/src/main/java/duke/tasks/Task.java b/src/main/java/duke/tasks/Task.java new file mode 100644 index 0000000000..b1cf0c2689 --- /dev/null +++ b/src/main/java/duke/tasks/Task.java @@ -0,0 +1,66 @@ +package duke.tasks; + +/** + * The Task Class is a Abstract Base class for any extending class that acts as + * a Task object for the Duke Chatbot + */ +abstract class Task { + // SEPERATION Attribute is used to encode the different attributes of the Task Class + public static final String SEPERATOR = "#sep#"; + protected final String description; + protected boolean isDoneTask; + + /** + * Constructor for a Task + * @param description String description of the task + * @param done Done Status of the task + */ + protected Task(String description, boolean done) { + this.description = description; + isDoneTask = done; + } + + /** + * Returns the check for if the task is already done. + * @return Boolean representing whether the task is done + */ + public boolean done() { + return isDoneTask; + } + + /** + * Mark a generic Task object as done + */ + public void doTask() { + isDoneTask = true; + } + + /** + * Get the description of the task + * @return description of task + */ + public String getDescription() { + return description; + } + + /** + * Checklist icon for displaying in the toString Representation. + * @return either done or not done + */ + public String statusIcon() { + return isDoneTask ? "[\u2713] " : "[\u2718] "; + } + + @Override + public String toString() { + return statusIcon() + getDescription(); + } + + /** + * Takes done status and attributes to encode the String + * @return a encoded string version of task for writing to text file. + */ + public String saveTask() { + return isDoneTask + SEPERATOR + description; + } +} diff --git a/src/main/java/duke/tasks/TaskIoParser.java b/src/main/java/duke/tasks/TaskIoParser.java new file mode 100644 index 0000000000..55e24326f1 --- /dev/null +++ b/src/main/java/duke/tasks/TaskIoParser.java @@ -0,0 +1,106 @@ +package duke.tasks; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +import duke.exceptions.DukeDateTimeException; +import duke.exceptions.DukeIoException; + + +/** + * Class to perform reading and writing operations on the task list itself. + */ +public class TaskIoParser { + private static final String SAVEFILE = "save.txt"; + private final File saveFile; + + TaskIoParser(String path) { + Path taskFile = Paths.get(path, "src", "save"); + this.saveFile = new File(taskFile.toString()); + } + + /** + * Load duke.tasks from a text file into memory + * @return A read text file of duke.tasks to Tasklist + * @throws DukeIoException if there is an error in reading a file from disk + */ + public List loadTaskList() throws DukeIoException { + List tasks = new ArrayList<>(); + try { + Scanner sc = new Scanner(this.saveFile.toPath().resolve(SAVEFILE)); + String currentLine = ""; + String[] spl; + while (sc.hasNext()) { + try { + currentLine = sc.nextLine(); + spl = currentLine.split(Task.SEPERATOR); + switch (spl[0]) { + case "T": + tasks.add(new ToDo(spl[2], Boolean.parseBoolean(spl[1]))); + break; + case "D": + tasks.add(new Deadline(spl[2], spl[3], Boolean.parseBoolean(spl[1]))); + break; + case "E": + tasks.add(new Event(spl[2], spl[3], Boolean.parseBoolean(spl[1]))); + break; + default: + continue; + } + } catch (DukeDateTimeException ignored) { + // ignored as if the error occurs, we just do not parse that command + } + } + sc.close(); + return tasks; + } catch (IOException fileException) { + throw new DukeIoException("Oops we couldnt read any file," + + " hence we will start from a new save file"); + } + + } + + /** + * For initialising new TaskList + * @return returns a new List of Tasks + */ + public List loadNewTaskList() { + return new ArrayList<>(); + } + + /** + * Writes the task list into a textfile + * @param taskList the given list of duke.tasks. + * @throws DukeIoException If there is a IO error in creating or writing to the file. + */ + public void writeTask(List taskList) throws DukeIoException { + if (!saveFile.exists()) { + try { + Files.createDirectories(Path.of(saveFile.getPath())); + Files.createFile(Path.of(saveFile.getPath()).resolve(SAVEFILE)); + } catch (IOException e) { + throw new DukeIoException("Could not save the file due to directory not created"); + } + } + + try { + FileWriter fw = new FileWriter(Path.of(saveFile.getPath()).resolve(SAVEFILE).toFile()); + String linesep = System.lineSeparator(); + for (Task t : taskList) { + fw.write(t.saveTask()); + fw.write(linesep); + } + fw.close(); + } catch (IOException e) { + throw new DukeIoException(e.toString()); + } + + } +} diff --git a/src/main/java/duke/tasks/TaskManager.java b/src/main/java/duke/tasks/TaskManager.java new file mode 100644 index 0000000000..a0fafee7d3 --- /dev/null +++ b/src/main/java/duke/tasks/TaskManager.java @@ -0,0 +1,162 @@ +package duke.tasks; + + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; + +import duke.exceptions.DukeCommandException; +import duke.exceptions.DukeIndexException; +import duke.exceptions.DukeIoException; + +/** + * TaskManager is a class to handle where Tasks are CRUD. + */ +public class TaskManager { + private final List taskList; + private final TaskIoParser ioParser; + + /** + * Constructs TaskManager for the Duke Application with loading from the save file + * @param path File Path from Main Class + * @throws DukeIoException If no loaded save is read + */ + public TaskManager(String path) throws DukeIoException { + ioParser = new TaskIoParser(path); + taskList = ioParser.loadTaskList(); + } + + /** + * Constructs TaskManger for the first time. + * @param path File Path from Main Class + * @param isNew Boolean to indicate that the TaskManager is first initialised. + */ + public TaskManager(String path, boolean isNew) { + assert isNew : "isNew is to allow for polymorphism for the case where taskmanager is new"; + ioParser = new TaskIoParser(path); + taskList = ioParser.loadNewTaskList(); + } + /** + * Indicates that a task is done + * @param index index of the list as displayed from the application + * @return String representation of indicating the task is done. + * @throws DukeCommandException if a illegal index is given + * @throws DukeIndexException If a given index is out of bounds + */ + public String doTask(String index) throws DukeCommandException, DukeIndexException { + try { + int i = Integer.parseInt(index) - 1; + //0 indexing + getTask(i).doTask(); + return "\tNice! I've marked this task as done: \n\t" + getTask(i) + "\n"; + } catch (IllegalArgumentException e) { + throw new DukeCommandException(index); + } catch (IndexOutOfBoundsException e) { + throw new DukeIndexException(index, taskList.size()); + } + + } + + /** + * Deletes task + * @param index index of the list as displayed from the application + * @return String representation of the confirmation of deletion of task + * @throws DukeCommandException if a illegal index is given + * @throws DukeIndexException if a given index is out of bounds + */ + public String deleteTask(String index) throws DukeCommandException, DukeIndexException { + try { + int i = Integer.parseInt(index) - 1; + //0 indexing + Task t = getTask(i); + taskList.remove(i); + return new StringBuilder().append("\tNoted! I've removed this task from your list: \n\t") + .append(t) + .append("\n\tNow you have ") + .append(taskList.size()) + .append("tasks in the list.\n").toString(); + } catch (IllegalArgumentException e) { + throw new DukeCommandException(index); + } catch (IndexOutOfBoundsException e) { + throw new DukeIndexException(index, taskList.size()); + } + + } + + /** + * Get task from the internal list + * @param index index of the internal list + * @return Task the task at that index + */ + private Task getTask(int index) { + return taskList.get(index); + } + + /** + * generic polymorphic data flow for adding a task to the runtime database + * @param t task + * @return String to be wrapped and printed + */ + public String add(Task t) { + taskList.add(t); + return echo(t); + } + + /** + * Returns string builder of the task + * + * @param task Task + * @return String echow when task is completed + */ + private String echo(Task task) { + return new StringBuilder().append("\tGot it. I've added this task:\n\t ") + .append(task).append("\n\tNow you have ") + .append(taskList.size()) + .append(" tasks in the list.\n") + .toString(); + } + + /** + * Message Passing for Tasks + * @throws DukeIoException if the task cannot be read to the file + */ + public void saveTasks() throws DukeIoException { + ioParser.writeTask(taskList); + } + /** + * Parses the current list and prints the output + * @return String representation of the Task List + */ + public String listTasks() { + return findTasks(""); + } + /** + * Regex pattern string search + * @param pattern Regex Pattern or substring of description of any task in the list + * @return String representation of duke.tasks that match the given pattern + */ + public String findTasks(String pattern) { + StringBuilder sb = new StringBuilder(""); + if (taskList.size() > 0) { + Pattern stringPattern = Pattern.compile(pattern); + AtomicInteger index = new AtomicInteger(); + sb.append(taskList.stream() + .filter(task -> { + index.incrementAndGet(); + return stringPattern.matcher(task.getDescription()).find(); + }).map(task -> String.format("\t%d. %s\n", index.get(), task)) + .reduce("" , (accumulate, next) -> accumulate + next)); + if (sb.toString().isEmpty()) { + sb.append("\tCannot find a valid task in your list"); + } + } else { + sb.append("\tThere are no tasks in your list!\n"); + } + return sb.toString(); + } + + @Override + public String toString() { + return "TaskManager: \n" + listTasks(); + } +} diff --git a/src/main/java/duke/tasks/TextParser.java b/src/main/java/duke/tasks/TextParser.java new file mode 100644 index 0000000000..c3ee3e0786 --- /dev/null +++ b/src/main/java/duke/tasks/TextParser.java @@ -0,0 +1,38 @@ +package duke.tasks; + +import java.util.Arrays; +import java.util.Optional; + +import duke.command.CommandHelp; + +/** + * TextParser to handle parsing of commands and possible cleaning. + */ +public class TextParser { + public TextParser(){ + } + /** + * Parse String Input into the Command Parser to return a Enum of the command encoded. + * + * @param cmd the string command for the Duke Application + * @return Command Enumeration + */ + public CommandHelp parseHelpCommand(String cmd) { + String cleaned = cmd.toLowerCase(); + Optional given = Arrays.stream(CommandHelp.values()) + .filter(commandHelp -> commandHelp.getCode().equals(cleaned)) + .findFirst(); + given = given.isEmpty() ? Optional.of(CommandHelp.ERROR) : given; + return given.get(); + } + /** + * inputs string, processes and cleans the text for the chatbot + * via adding a ending token seperator + * @param userInput Direct user input of the string + * @return Cleaned user input + */ + public String cleanInput(String userInput) { + return userInput.strip(); + } + +} diff --git a/src/main/java/duke/tasks/TimedTask.java b/src/main/java/duke/tasks/TimedTask.java new file mode 100644 index 0000000000..34f745b794 --- /dev/null +++ b/src/main/java/duke/tasks/TimedTask.java @@ -0,0 +1,89 @@ +package duke.tasks; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.Period; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; + +import duke.exceptions.DukeDateTimeException; +/** + * TimedTask is a abstract class that inherits from the base Task + * class, to add a new field of datetime into this child class which implements such functionalities + */ +abstract class TimedTask extends Task { + protected static final DateTimeFormatter FMAT; + protected static final LocalDate NOW = LocalDateTime.now().toLocalDate(); + static { + DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder(); + dateTimeFormatterBuilder.appendPattern("dd-MM-yyyy"); + dateTimeFormatterBuilder.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0); + dateTimeFormatterBuilder.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0); + dateTimeFormatterBuilder.parseDefaulting(ChronoField.HOUR_OF_DAY, 0); + dateTimeFormatterBuilder.parseDefaulting(ChronoField.YEAR_OF_ERA, LocalDateTime.now().getYear()); + dateTimeFormatterBuilder.parseDefaulting(ChronoField.MONTH_OF_YEAR, LocalDateTime.now().getMonthValue()); + dateTimeFormatterBuilder.parseDefaulting(ChronoField.DAY_OF_MONTH, LocalDateTime.now().getDayOfMonth()); + FMAT = dateTimeFormatterBuilder + .toFormatter(); + } + protected final LocalDateTime dateby; + /** + * Constructor for a TimedTask for use by implementing subclasses, in particular to populate + * Tasks that have been read from a text save file. + * @param desc Description of a task + * @param date String that is extracted from user input to be parsed into the constructor for a TimedTask object + * @param done Done Status of a task + * @throws DukeDateTimeException if the fields for the date are not matching autocorrection cases + */ + protected TimedTask(String desc, String date, Boolean done) throws DukeDateTimeException { + super(desc, done); + try { + if (NOW.format(FMAT).length() > date.length()) { + date = date + NOW.format(FMAT).substring(date.length()); + } + dateby = LocalDateTime.parse(date, FMAT); + } catch (DateTimeParseException e) { + throw new DukeDateTimeException("The String you entered does not meet the " + + "required format of 'yyyy-MM-dd' "); + } + + } + + /** + * Constructor for a TimedTask for use by implementing subclasses, in particular to create + * a new TimedTask class + * @param desc Description of a task + * @param date String that is extracted from user input to be parsed into the constructor for + * a TimedTask object + * @throws DukeDateTimeException if the fields for the date are not matching autocorrection cases + */ + protected TimedTask(String desc, String date) throws DukeDateTimeException { + this(desc, date, false); + } + /** + * Performs a DateTime Arithmetric calculation with the current time of execution of the program + * in order to display a countdown of days in the Task + * @return integer representing the number of days left or past since the task was due. + */ + protected int timeLeft() { + return Period.between(NOW, LocalDate.from(dateby)).getDays(); + } + + /** + * Get the dateby for the set task + * @return dateby for the registered task + */ + public String getDateby() { + return dateby.toLocalDate().format(FMAT); + } + /** + * Returns a String Representation of the Event object class to write to text file. + * @return the saved task to write to a text file + */ + @Override + public String saveTask() { + return super.saveTask() + SEPERATOR + getDateby(); + } +} diff --git a/src/main/java/duke/tasks/ToDo.java b/src/main/java/duke/tasks/ToDo.java new file mode 100644 index 0000000000..9f3bd5effd --- /dev/null +++ b/src/main/java/duke/tasks/ToDo.java @@ -0,0 +1,37 @@ +package duke.tasks; + +/** + * ToDo is a Implementation of the Task Class with no additional fields + * This is the class that stores todo duke.tasks in the Duke program + */ +public class ToDo extends Task { + /** + * Constructs the ToDo Class for use when creating new duke.tasks + * by end user. + * @param desc Description of task + */ + public ToDo(String desc) { + super(desc, false); + } + /** + * Constructs the ToDo Class for use when populating the list of duke.tasks + * recorded by the save text file + * @param desc Description of task + * @param done Done Status of task + */ + ToDo(String desc, Boolean done) { + super(desc, done); + } + + @Override + public String toString() { + return "[T]" + super.toString(); + } + /** + * Returns a String Representation of the ToDo object class to write to text file. + * @return the saved task to write to a text file + */ + public String saveTask() { + return "T" + SEPERATOR + super.saveTask(); + } +} diff --git a/src/main/java/duke/ui/CommandLineInterface.java b/src/main/java/duke/ui/CommandLineInterface.java new file mode 100644 index 0000000000..744709e24a --- /dev/null +++ b/src/main/java/duke/ui/CommandLineInterface.java @@ -0,0 +1,75 @@ +package duke.ui; + +import java.util.Scanner; + +/** + * Class to handle Commandline interface, implements UserInterface contract. + */ +public class CommandLineInterface implements UserInterface { + private static final String logo = "\tHello from\n" + + " ____ _ \n" + + "| _ \\ _ _| | _____ \n" + + "| | | | | | | |/ / _ \\\n" + + "| |_| | |_| | < __/\n" + + "|____/ \\__,_|_|\\_\\___|\n" + + "\tHello! %s I'm Duke\n\tWhat can I do for you " + + "\n"; + private static final String goodbye = "Bye %s! Hope to see you again soon!\n"; + private static final String linebreaker = "_".repeat(30) + "\n"; + private boolean isChatbotRunning; + private String userName; + private final Scanner scanner; + + /** + * Constructs the CommandLineInterface UI for use without a GUI + */ + public CommandLineInterface() { + scanner = new Scanner(System.in); + isChatbotRunning = false; + } + @Override + public boolean isRunning() { + return isChatbotRunning; + } + /** + * Greeting from Duke Bot and set username of user + * @param userName Name of the user + */ + @Override + public void start(String userName) { + assert !isChatbotRunning : "CommandLineInterface should only start once"; + isChatbotRunning = true; + userName = userName; + systemMessage(String.format(logo, userName)); + } + + @Override + public void close() { + assert isChatbotRunning : "CommandLineInterface should only end once"; + scanner.close(); + isChatbotRunning = false; + systemMessage(String.format(goodbye, userName)); + } + + @Override + public String nextLine() { + return scanner.nextLine(); + } + + /** + * Pass Message into the System for display as text + * @param message from duke to human + */ + @Override + public void systemMessage(String message) { + System.out.print("\t" + linebreaker + indent(message) + linebreaker); + } + /** + * Indents text + * @param s text to indent + * @return indented text + */ + private String indent(String s) { + return " " + s.replace("\n", "\n\t"); + } +} diff --git a/src/main/java/duke/ui/GuiHelper.java b/src/main/java/duke/ui/GuiHelper.java new file mode 100644 index 0000000000..024a13bdd1 --- /dev/null +++ b/src/main/java/duke/ui/GuiHelper.java @@ -0,0 +1,82 @@ +package duke.ui; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Container for messages from {@link duke.Duke} to the {@link MainLauncher} + */ +public class GuiHelper implements UserInterface { + private boolean isChatbotRunning = true; + private String userName; + private String userInput; + private List commandOutput; + private boolean isNotConsumed = false; + /** + * Constructs helper class for GUI Interfacing + */ + public GuiHelper() { + commandOutput = new ArrayList<>(); + } + + @Override + public void start(String userName) { + assert !isChatbotRunning : "GuiHelper should start only once"; + isChatbotRunning = true; + userName = userName; + } + @Override + public boolean isRunning() { + return isChatbotRunning; + } + + @Override + public void close() { + assert isChatbotRunning : "GuiHelper should only close once"; + isChatbotRunning = false; + commandOutput.add("Goodbye " + userName + " my friend!"); + } + + @Override + public String nextLine() { + return userInput; + } + + @Override + public void systemMessage(String message) { + isNotConsumed = true; + commandOutput.add(message); + } + + /** + * Consumes command and returns output from Duke command if any. + * Otherwise returns Optional.empty() + * If a output is returned, mark as "consumed" + * and subsequent calls to consumeCommandOutput returns Optional.empty() + * until a new {@link duke.command.CommandEnums} from Duke is run + * @return Output from Duke Command if it is first invocation, else optional.empty. + */ + public Optional> consumeCommandOutput() { + if (isNotConsumed) { + List result = commandOutput; + isNotConsumed = false; + commandOutput = new ArrayList<>(); + return Optional.of(result); + } else { + return Optional.empty(); + } + } + + /** + * Set user input + * @param input userInput + */ + public void setUserInput(String input) { + userInput = input; + } + + public String toString() { + return "GUI"; + } +} diff --git a/src/main/java/duke/ui/MainLauncher.java b/src/main/java/duke/ui/MainLauncher.java new file mode 100644 index 0000000000..c4ba649d63 --- /dev/null +++ b/src/main/java/duke/ui/MainLauncher.java @@ -0,0 +1,32 @@ +package duke.ui; + +import java.io.IOException; + +import duke.ui.graphics.MainWindow; +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + + + +/** + * Main class for GUI + */ +public class MainLauncher extends Application { + @Override + public void start(Stage stage) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/MainWindow.fxml")); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + stage.setScene(scene); + stage.setTitle("Duke Chatbot"); + fxmlLoader.getController().setup(); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/duke/ui/UserInterface.java b/src/main/java/duke/ui/UserInterface.java new file mode 100644 index 0000000000..3cd15ad58f --- /dev/null +++ b/src/main/java/duke/ui/UserInterface.java @@ -0,0 +1,12 @@ +package duke.ui; + +/** + * Class to define the UI Operations + */ +public interface UserInterface { + public boolean isRunning(); + public void start(String username); + public void close(); + public String nextLine(); + public void systemMessage(String message); +} diff --git a/src/main/java/duke/ui/graphics/DialogBox.java b/src/main/java/duke/ui/graphics/DialogBox.java new file mode 100644 index 0000000000..f5c6ad86fb --- /dev/null +++ b/src/main/java/duke/ui/graphics/DialogBox.java @@ -0,0 +1,76 @@ +package duke.ui.graphics; + +import java.io.IOException; +import java.net.URL; +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; + + + +/** + * Dialog box contains messages from duke and user + */ +public class DialogBox extends HBox { + @FXML + private Label dialog; + @FXML + private ImageView displayPicture; + + private DialogBox(String text, Image img) { + try { + URL file = MainWindow.class.getResource("/view/DialogBox.fxml"); + FXMLLoader fxmlLoader = new FXMLLoader(file); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + dialog.setMinHeight(Label.USE_PREF_SIZE); + dialog.setText(text); + displayPicture.setImage(img); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + ObservableList tmp = FXCollections.observableArrayList(getChildren()); + Collections.reverse(tmp); + getChildren().setAll(tmp); + setAlignment(Pos.TOP_LEFT); + } + + /** + * Constructs a Dialog box for the user + * @param text text to input + * @param img image for user + * @return Dialog box for GUI + */ + public static DialogBox getUserDialog(String text, Image img) { + return new DialogBox(text, img); + } + + /** + * Constructs a dialog box for the duke response + * @param text text output + * @param img image for duke + * @return Dialog box for GUI + */ + 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/ui/graphics/MainWindow.java b/src/main/java/duke/ui/graphics/MainWindow.java new file mode 100644 index 0000000000..5a94cac264 --- /dev/null +++ b/src/main/java/duke/ui/graphics/MainWindow.java @@ -0,0 +1,84 @@ +package duke.ui.graphics; + +import duke.Duke; +import duke.ui.GuiHelper; +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.stage.Stage; + +public class MainWindow extends AnchorPane { + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogContainer; + @FXML + private TextField userInput; + @FXML + private Button sendButton; + + private GuiHelper guiHelper; + private Duke duke; + + private final Image userImg = new Image(getClass().getResourceAsStream("/images/caocao.gif")); + private final Image dukeImg = new Image(getClass().getResourceAsStream("/images/trump.gif")); + + /** + * Substitute constructor for GUI + */ + public void setup() { + guiHelper = new GuiHelper(); + duke = new Duke(guiHelper); + greeting(); + } + + /** + * Initialise the Mainwindow. + */ + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + } + /** + * 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(); + guiHelper.setUserInput(input); + userMessage(input); + duke.nextIteration(); + if (!guiHelper.isRunning()) { + closeWindow(); + } + guiHelper.consumeCommandOutput().ifPresent((message) -> { + message.forEach(this::dukeMessage); + }); + userInput.clear(); + } + + private void greeting() { + final String welcomeMessage = "Hello Friend! I'm Duke, how may I help you!"; + dukeMessage(welcomeMessage); + } + + private void closeWindow() { + Stage stage = (Stage) sendButton.getScene().getWindow(); + stage.close(); + } + + private void userMessage(String message) { + dialogContainer.getChildren().add(DialogBox.getUserDialog(message , userImg)); + } + + private void dukeMessage(String message) { + dialogContainer.getChildren().add(DialogBox.getDukeDialog(message, dukeImg)); + } + +} + diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png new file mode 100644 index 0000000000..d893658717 Binary files /dev/null and b/src/main/resources/images/DaDuke.png differ diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png new file mode 100644 index 0000000000..3c82f45461 Binary files /dev/null and b/src/main/resources/images/DaUser.png differ diff --git a/src/main/resources/images/caocao.gif b/src/main/resources/images/caocao.gif new file mode 100644 index 0000000000..ec7da83dda Binary files /dev/null and b/src/main/resources/images/caocao.gif differ diff --git a/src/main/resources/images/trump.gif b/src/main/resources/images/trump.gif new file mode 100644 index 0000000000..47a52d4cf4 Binary files /dev/null and b/src/main/resources/images/trump.gif differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..4d7524da9b --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..153572f0fc --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + +