diff --git a/README.md b/README.md index 9d95025bce..e0a0b00ba7 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,245 @@ -# Duke project template - -This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. - -## Setting up in Intellij - -Prerequisites: JDK 11, update Intellij to the most recent version. - -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 - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - ``` +# User Guide +Duke is a desktop app for managing tasks. Duke keeps track of ongoing and finished tasks. Duke offers an interactive and easy way to manage your time. + +## Quick Start + +1. Download `duke.jar` file from [github](https://github.com/ijavierja/ip/releases). + +2. Ensure you have Java `11` or above installed. + +3. Choose the home folder and make a copy of the file there. + +4. Double-click the file to start the app. + +5. A GUI should start.
+ ![Ui](docs/Ui.PNG) + +6. Refer to the [Usage](#usage) below for the details of each command. + +## Features + +### Task Management +* Add tasks +* Delete tasks +* Mark task as done +* Tag tasks + +### Task Types +* Todo +* Event +* Deadline + +### View Tasks +* view all tasks in the current task list + +### Interactive Design +* Greets user when Duke is started up + +``` + ____ _ +| _ \ _ _| | _____ +| | | | | | | |/ / _ \ +| |_| | |_| | < __/ +|____/ \__,_|_|\_\___| + Hello I'm Duke + What can I do for you? +``` +* Gives user a response when commands are inputted +* Tells user when input is incorrect + +## Usage + +### `list` - List Command + +Lists all tasks in the current task list numerically. +The details, such as tags, date & time, other information, of the tasks are also shown + +Example of usage: + +`list` + +Expected outcome: + +``` +Here are the tasks in your list: +1. [D][✗] iP (by: 16 Sep 2020, 11:59 PM) #OMFG +2. [T][✓] eat dinner #hungry +3. [E][✗] Graduation! (at: 2021 ) +``` + +### `todo` - Todo Task Command + +Creates and adds an ongoing todo task to the list. + +Format: + +`todo ` + +Example of usage: + +`todo eat dinner` + +Expected outcome: + +``` +Got it. I've added this task: + [T][✗] eat dinner +Now you have 4 tasks in the list. +``` + +### `event` - Event Task Command + +Creates and adds an ongoing event task to the list. + +Format: + +`event /at ` + +Example of usage: + +`event my birthday /at my place` + +Expected outcome: + +``` +Got it. I've added this task: + [E][✗] my birthday (at: my place) +Now you have 5 tasks in the list. +``` + +### `deadline` - Deadline Task Command + +Creates and adds an ongoing deadline task to the list. + +Format: + +`event /by ` + +date&time format: + + `dd/mm/yyyy tttt` + +* Note: no need to write 0 for day and month + +* 02/09/2020 can be written as 2/9/2020 + +Example of usage: + +`deadline user guide /by 17/9/2020 2359` + +Expected outcome: + +``` +Got it. I've added this task: + [D][✗] user guide (by: 17 Sep 2020, 11:59 PM) +Now you have 6 tasks in the list. +``` + +### `delete` - Delete Command + +Deletes a task from the task list specified by task's index shown using list command. + +Format: + +`delete ` + +Example of usage: + +`delete 6` + +Expected outcome: + +``` +Noted. I've removed this task: + [D][✗] user guide (by: 17 Sep 2020, 11:59 PM) +Now you have 5 tasks in the list. +``` + +### `done` - Done Command + +Marks a task as done. Task is specified using task's index shown using list command. + +Format: + +`done ` + +Example of usage: + +`done 5` + +Expected outcome: + +``` +Nice! I've marked this task as done: + [E][✓] my birthday (at: my place) +``` + +### `/tag` - tag notation + +Note tasks with a tag when creating new tasks. + +Format: +` /tag ` + +Example of usage: + +`deadline user guide /by 17/9/2020 2359 /tag GG` + +Expected outcome: + +``` +Got it. I've added this task: + [D][✗] user guide (by: 17 Sep 2020, 11:59 PM) #GG +Now you have 6 tasks in the list. +``` + +Note: +* /tag must be added at the end of input +* the tag must be alphanumeric + +### `save` - Save Command + +Saves the current task list to the hard drive. + +Example of usage: + +`save` + +Expected outcome: + +``` +Tasks have been saved! +``` + +### `find` - Find Command + +Finds and lists the tasks that have a matching sequence. + +Format: + +`find ` + +Example of usage: + +`find user guide` + +Expected outcome: + +``` +Here are the matching tasks in your list: +6.[D][✗] user guide (by: 17 Sep 2020, 11:59 PM) #GG +``` + +### `bye` - Bye Command + +Exits the program. + +Example of usage: + +`bye` + +Expected outcome: + +``` +Bye. Hope to see you again soon! +``` \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..c27759557c --- /dev/null +++ b/build.gradle @@ -0,0 +1,62 @@ +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.4.2' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.4.2' + + 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.gui.Launcher" +} + +shadowJar { + archiveBaseName = "duke" + archiveClassifier = null +} + +checkstyle { + toolVersion = '8.29' +} + +run{ + systemProperty "file.encoding", "utf-8" + standardInput = System.in +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..4c001417ae --- /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/data/tasks.txt b/data/tasks.txt new file mode 100644 index 0000000000..27cdaf2f39 --- /dev/null +++ b/data/tasks.txt @@ -0,0 +1,6 @@ +[D][✗] iP (by: 16 Sep 2020, 11:59 PM) #OMFG +[T][✓] eat dinner #hungry +[E][✗] Graduation! (at: 2021 ) #/n +[T][✗] eat dinner#/n +[E][✓] my birthday (at: my place) #/n +[D][✗] user guide (by: 17 Sep 2020, 11:59 PM) #GG diff --git a/docs/README.md b/docs/README.md index fd44069597..732299822b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,20 +1,245 @@ # User Guide +Duke is a desktop app for managing tasks. Duke keeps track of ongoing and finished tasks. Duke offers an interactive and easy way to manage your time. + +## Quick Start + +1. Download `duke.jar` file from [github](https://github.com/ijavierja/ip/releases). + +2. Ensure you have Java `11` or above installed. + +3. Choose the home folder and make a copy of the file there. + +4. Double-click the file to start the app. + +5. A GUI should start.
+ ![Ui](Ui.png) + +6. Refer to the [Usage](#usage) below for the details of each command. ## Features -### Feature 1 -Description of feature. +### Task Management +* Add tasks +* Delete tasks +* Mark task as done +* Tag tasks + +### Task Types +* Todo +* Event +* Deadline + +### View Tasks +* view all tasks in the current task list + +### Interactive Design +* Greets user when Duke is started up + +``` + ____ _ +| _ \ _ _| | _____ +| | | | | | | |/ / _ \ +| |_| | |_| | < __/ +|____/ \__,_|_|\_\___| + Hello I'm Duke + What can I do for you? +``` +* Gives user a response when commands are inputted +* Tells user when input is incorrect ## Usage -### `Keyword` - Describe action +### `list` - List Command + +Lists all tasks in the current task list numerically. +The details, such as tags, date & time, other information, of the tasks are also shown + +Example of usage: + +`list` + +Expected outcome: + +``` +Here are the tasks in your list: +1. [D][✗] iP (by: 16 Sep 2020, 11:59 PM) #OMFG +2. [T][✓] eat dinner #hungry +3. [E][✗] Graduation! (at: 2021 ) +``` + +### `todo` - Todo Task Command + +Creates and adds an ongoing todo task to the list. + +Format: + +`todo ` + +Example of usage: + +`todo eat dinner` + +Expected outcome: + +``` +Got it. I've added this task: + [T][✗] eat dinner +Now you have 4 tasks in the list. +``` + +### `event` - Event Task Command + +Creates and adds an ongoing event task to the list. + +Format: + +`event /at ` + +Example of usage: + +`event my birthday /at my place` + +Expected outcome: + +``` +Got it. I've added this task: + [E][✗] my birthday (at: my place) +Now you have 5 tasks in the list. +``` + +### `deadline` - Deadline Task Command + +Creates and adds an ongoing deadline task to the list. + +Format: + +`event /by ` + +date&time format: + + `dd/mm/yyyy tttt` + +* Note: no need to write 0 for day and month + +* 02/09/2020 can be written as 2/9/2020 + +Example of usage: + +`deadline user guide /by 17/9/2020 2359` + +Expected outcome: + +``` +Got it. I've added this task: + [D][✗] user guide (by: 17 Sep 2020, 11:59 PM) +Now you have 6 tasks in the list. +``` + +### `delete` - Delete Command + +Deletes a task from the task list specified by task's index shown using list command. + +Format: + +`delete ` + +Example of usage: + +`delete 6` + +Expected outcome: + +``` +Noted. I've removed this task: + [D][✗] user guide (by: 17 Sep 2020, 11:59 PM) +Now you have 5 tasks in the list. +``` + +### `done` - Done Command + +Marks a task as done. Task is specified using task's index shown using list command. + +Format: + +`done ` + +Example of usage: + +`done 5` + +Expected outcome: + +``` +Nice! I've marked this task as done: + [E][✓] my birthday (at: my place) +``` + +### `/tag` - tag notation + +Note tasks with a tag when creating new tasks. + +Format: +` /tag ` + +Example of usage: + +`deadline user guide /by 17/9/2020 2359 /tag GG` + +Expected outcome: + +``` +Got it. I've added this task: + [D][✗] user guide (by: 17 Sep 2020, 11:59 PM) #GG +Now you have 6 tasks in the list. +``` + +Note: +* /tag must be added at the end of input +* the tag must be alphanumeric + +### `save` - Save Command + +Saves the current task list to the hard drive. + +Example of usage: + +`save` + +Expected outcome: + +``` +Tasks have been saved! +``` + +### `find` - Find Command + +Finds and lists the tasks that have a matching sequence. + +Format: + +`find ` + +Example of usage: + +`find user guide` + +Expected outcome: + +``` +Here are the matching tasks in your list: +6.[D][✗] user guide (by: 17 Sep 2020, 11:59 PM) #GG +``` + +### `bye` - Bye Command -Describe action and its outcome. +Exits the program. Example of usage: -`keyword (optional arguments)` +`bye` Expected outcome: -`outcome` +``` +Bye. Hope to see you again soon! +``` \ No newline at end of file diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..a7d8f7d023 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 index 5d313334cc..7c5f8b6bf8 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,10 +1,97 @@ +package duke; + +import duke.ui.Ui; +import duke.task.TaskList; +import duke.storage.Storage; +import duke.exception.DukeException; +import duke.command.Command; +import duke.parser.Parser; + +/** + * Class contains main method of the Duke application. + * Duke manages, stores and track tasks as specified by the user. + */ public class Duke { + private static String TASKS_PATHNAME = "data/tasks.txt"; + + private Ui ui; + private TaskList taskList; + private Storage storage; + + /** + * Constructor with no param. + * The filepath for storage is set as the static variable, TASKS_PATHNAME. + */ + public Duke() { + ui = new Ui(); + storage = new Storage(TASKS_PATHNAME); + try { + taskList = new TaskList(storage.load()); + } catch (DukeException e) { + ui.showOutput(e.getMessage()); + //ui.showLoadingError(); + taskList = new TaskList(); + } + } + + /** + * main method of Duke + * Uses the Ui class as the medium for interaction with user. + * Ui class uses command line to interact with user. (System.in and System.out). + * @param args + */ public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); + new Duke().run(); + } + + public void run() { + ui.showLine(); + System.out.println(ui.welcome()); + ui.showLine(); + boolean isExit = false; + while (!isExit) { + try { + String fullCommand = ui.readCommand(); + ui.showLine(); // show the divider line ("_______") + Command c = Parser.parse(fullCommand); + ui.showOutput(c.execute(taskList, ui, storage)); + isExit = c.isExit(); + } catch (DukeException e) { + ui.showError(e.getMessage()); + } finally { + ui.showLine(); + } + } + } + + /** + * Returns the greeting for Gui + * @return + */ + public String welcomeGui() { + return "[in bird language]\n" + + "Hello there! I'm Secretary Bird! *chirps warmly*\n" + + "How can I help you? *chirps curiously*\n" + + "P.S. My species is literally named secretary bird!\n" + + "*chirps punly*\n"; + } + + /** + * Returns the response by the duke program as a string. + * This method is used by Gui to get response from duke program. + * @param input + * @return + */ + public String getResponse(String input) { + try{ + Command c = Parser.parse(input); + assert c != null : "Command c has not been instantiated."; + assert taskList != null : "TaskList does not exist."; + assert ui != null : "Ui does not exist."; + assert storage != null : "Storage does not exist."; + return c.execute(taskList, ui, storage); + } catch (DukeException e) { + return e.getMessage(); + } } } diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..2c9a9745c5 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: duke.Duke + diff --git a/src/main/java/command/Command.java b/src/main/java/command/Command.java new file mode 100644 index 0000000000..54d682f7f7 --- /dev/null +++ b/src/main/java/command/Command.java @@ -0,0 +1,15 @@ +package duke.command; + +import duke.task.TaskList; +import duke.ui.Ui; +import duke.storage.Storage; + +/** + * Interface for Commands + * Implementation have to overwrite execute and isExit methods. + */ + +public interface Command { + String execute(TaskList taskList, Ui ui, Storage storage); + boolean isExit(); +} diff --git a/src/main/java/command/CommandBye.java b/src/main/java/command/CommandBye.java new file mode 100644 index 0000000000..22e1f1b536 --- /dev/null +++ b/src/main/java/command/CommandBye.java @@ -0,0 +1,24 @@ +package duke.command; + +import duke.task.TaskList; +import duke.ui.Ui; +import duke.storage.Storage; + +/** + * Command to exit duke + * + * This command only displays the exit statement while using GUI and does not exit the program. + */ + +public class CommandBye implements Command{ + @Override + public String execute(TaskList taskList, Ui ui, Storage storage) { + String string = "Bye. Hope to see you again soon!"; + return string; + } + + @Override + public boolean isExit() { + return true; + } +} diff --git a/src/main/java/command/CommandDeadline.java b/src/main/java/command/CommandDeadline.java new file mode 100644 index 0000000000..8571ccd7c5 --- /dev/null +++ b/src/main/java/command/CommandDeadline.java @@ -0,0 +1,138 @@ +package duke.command; + +import duke.task.TaskList; +import duke.ui.Ui; +import duke.storage.Storage; +import duke.task.Task; +import duke.task.TaskType; +import duke.exception.DukeException; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.FormatStyle; +import java.util.Optional; + +/** + * Deadline Command + * Requires a description of the deadline and a valid date for the task. + */ + +public class CommandDeadline implements Command { + private final String fullCommand; + + public CommandDeadline(String fullCommand) { + this.fullCommand = fullCommand; + } + + @Override + public String execute(TaskList taskList, Ui ui, Storage storage) { + assert fullCommand.length() <= 9 : "deadline: string is too short."; + String input = fullCommand.substring(8); + try { + String[] inputAndTag = Task.getTagFromString(input); + input = inputAndTag[0]; + String tag = inputAndTag[1]; + int index = input.indexOf("/by "); + + //No /by + if (!hasDate(index)) { + return "☹ OOPS!!! The description of a deadline must have a indicated deadline."; + } + + //No description + if (!hasDescription(index, input)) { + return "☹ OOPS!!! The description of a deadline cannot be empty."; + } + + if (hasRestrictedPhrase(input)) { + return "☹ OOPS!!! Restricted phrase used"; + } + + String dateTimeString = toLocalDateTimeReadible(index, input); + StringBuilder outputStringBuilder = new StringBuilder(); + //Try to input date if it a valid is date is used. + try { + LocalDateTime localDateTime = LocalDateTime.parse(dateTimeString, DateTimeFormatter.ISO_LOCAL_DATE_TIME); + + String taskString = toTaskString(index, input, localDateTime); + + Task task = new Task(TaskType.DEADLINE, false, taskString, Optional.of(localDateTime), tag); + taskList.add(task); + + outputStringBuilder.append("Got it. I've added this task:").append("\n"); + + outputStringBuilder.append(" ").append(task.toFullOutputString()).append("\n"); + + outputStringBuilder.append("Now you have " + taskList.size() + " tasks in the list."); + + return outputStringBuilder.toString(); + } catch (DateTimeParseException e) { + + outputStringBuilder + .append("☹ OOPS!!! The description of a deadline must have a valid date and time. ") + .append("(Format: /by dd/mm/yyyy tttt e.g 2/12/2019 1800"); + return outputStringBuilder.toString(); + } + } catch (DukeException e) { + return e.getMessage(); + } + } + + @Override + public boolean isExit() { + return false; + } + + public boolean hasDate(int index) { + return (index != -1); + } + + public boolean hasDescription(int index, String input) { + return (index > 1) && !input.isEmpty(); + } + + public boolean hasRestrictedPhrase(String input) { + return input.contains("(by: "); + } + + public String toLocalDateTimeReadible(int index, String input) { + StringBuilder dateStringBuilder = new StringBuilder(); + String[] strings; + if(input.length() < index + 4) { //checks for Exception + return ""; + } + + strings = input.substring(index + 4).split("/"); + if (strings.length < 3) { + return ""; + } + if (strings[2].length() < 9 ) { + return ""; + } + if(strings[0].length() < 2) { + strings[0] = "0" + strings[0]; + } + if(strings[1].length() < 2) { + strings[1] = "0" + strings[1]; + } + + dateStringBuilder.append(strings[2].substring(0, 4)) + .append("-").append(strings[1]) + .append("-").append(strings[0]) + .append("T") + .append(strings[2].substring(5, 7)) + .append(":") + .append(strings[2].substring(7, 9)); + return dateStringBuilder.toString(); + } + + public String toTaskString(int index, String input,LocalDateTime localDateTime) { + StringBuilder taskStringBuilder = new StringBuilder(); + taskStringBuilder.append(input.substring(0, index)) + .append("(by: ") + .append(localDateTime.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT))) + .append(") "); + return taskStringBuilder.toString(); + } +} diff --git a/src/main/java/command/CommandDelete.java b/src/main/java/command/CommandDelete.java new file mode 100644 index 0000000000..2ab72252fc --- /dev/null +++ b/src/main/java/command/CommandDelete.java @@ -0,0 +1,73 @@ +package duke.command; + +import duke.task.TaskList; +import duke.task.Task; +import duke.ui.Ui; +import duke.storage.Storage; + +/** + * Delete Command + * + * Removes the task in the TaskList. + * The task is specified by their corresponding index number. + */ + +public class CommandDelete implements Command { + private final String fullCommand; + + public CommandDelete(String fullCommand) { + this.fullCommand = fullCommand; + } + + @Override + public String execute(TaskList taskList, Ui ui, Storage storage) { + assert fullCommand.length() <= 7 : "delete: string is too short."; + String input = fullCommand.substring(6); + StringBuilder outputStringBuilder = new StringBuilder(); + String temp; + if(input.isEmpty()) { + temp = "☹ OOPS!!! Please indicate which task is done."; + outputStringBuilder.append(temp); + return outputStringBuilder.toString(); + } + + input = input.substring(1); + boolean isInteger = true; + for(int charIndex = 0; charIndex < input.length(); charIndex++){ + if(!Character.isDigit(input.charAt(charIndex))){ + isInteger = false; + break; + } + } + if(!isInteger) { + temp = "☹ OOPS!!! Incorrect entry for finished task."; + outputStringBuilder.append(temp); + return outputStringBuilder.toString(); + } + + int tasknumber = Integer.parseInt(input); + if( (tasknumber == 0) ||(tasknumber > taskList.size()) ){ + temp = "☹ OOPS!!! Task number is not found in the list."; + outputStringBuilder.append(temp); + return outputStringBuilder.toString(); + } + + Task task = taskList.remove(tasknumber - 1); + + temp = "Noted. I've removed this task:"; + outputStringBuilder.append(temp).append("\n"); + + temp = " " + task.getTypeString() + task.getDoneString() + task.toString(); + outputStringBuilder.append(temp).append("\n"); + + temp = "Now you have " + taskList.size() + " tasks in the list."; + outputStringBuilder.append(temp); + + return outputStringBuilder.toString(); + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/command/CommandDone.java b/src/main/java/command/CommandDone.java new file mode 100644 index 0000000000..0ec7e00387 --- /dev/null +++ b/src/main/java/command/CommandDone.java @@ -0,0 +1,73 @@ +package duke.command; + +import duke.task.TaskList; +import duke.task.Task; +import duke.ui.Ui; +import duke.storage.Storage; + +/** + * Done Command + * + * Marks a task as done. + * The task is specified by its index number. + */ + +public class CommandDone implements Command { + private final String fullCommand; + + public CommandDone(String fullCommand) { + this.fullCommand = fullCommand; + } + + @Override + public String execute(TaskList taskList, Ui ui, Storage storage) { + assert fullCommand.length() <= 5 : "done: string is too short."; + String input = fullCommand.substring(4); + StringBuilder outputStringBuilder = new StringBuilder(); + String temp; + if(input.isEmpty()){ + temp = "☹ OOPS!!! Please indicate which task is done."; + outputStringBuilder.append(temp); + return outputStringBuilder.toString(); + } + + input = input.substring(1); + boolean isInteger = true; + for(int charIndex = 0; charIndex < input.length(); charIndex++){ + if(!Character.isDigit(input.charAt(charIndex))){ + isInteger = false; + break; + } + } + if(!isInteger) { + temp = "☹ OOPS!!! Incorrect entry for finished task."; + outputStringBuilder.append(temp); + return outputStringBuilder.toString(); + } + + int tasknumber = Integer.parseInt(input); + if( (tasknumber == 0) ||(tasknumber >taskList.size()) ){ + temp = "☹ OOPS!!! Task number is not found in the list."; + outputStringBuilder.append(temp); + return outputStringBuilder.toString(); + } + + Task task = taskList.remove(tasknumber - 1); + Task newTask = task.done(); + taskList.add(tasknumber - 1, newTask); + + + temp = "Nice! I've marked this task as done:"; + outputStringBuilder.append(temp).append("\n"); + + outputStringBuilder.append(" ").append(newTask.toFullOutputString()); + + + return outputStringBuilder.toString(); + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/command/CommandEvent.java b/src/main/java/command/CommandEvent.java new file mode 100644 index 0000000000..66f6a0f1f4 --- /dev/null +++ b/src/main/java/command/CommandEvent.java @@ -0,0 +1,67 @@ +package duke.command; + +import duke.task.TaskList; +import duke.task.Task; +import duke.ui.Ui; +import duke.storage.Storage; +import duke.task.TaskType; + +/** + * Event Command + * + * Creates a new Task of type event and adds it to taskList. + */ + +public class CommandEvent implements Command { + private final String fullCommand; + + public CommandEvent(String fullCommand) { + this.fullCommand = fullCommand; + } + + @Override + public String execute(TaskList taskList, Ui ui, Storage storage) { + assert fullCommand.length() <= 6 : "event: string is too short."; + String input = fullCommand.substring(5); + StringBuilder outputStringBuilder = new StringBuilder(); + String temp; + try { + String[] inputAndTag = Task.getTagFromString(input); + input = inputAndTag[0]; + String tag = inputAndTag[1]; + + if (input.isEmpty()) { + outputStringBuilder.append("☹ OOPS!!! The description of a event cannot be empty."); + return outputStringBuilder.toString(); + } + + int index = input.indexOf("/at "); + if (index == -1) { + outputStringBuilder.append("☹ OOPS!!! The description of an event must have a indicated deadline."); + return outputStringBuilder.toString(); + } + + StringBuilder taskStringBuilder = new StringBuilder(); + taskStringBuilder.append(input.substring(0, index)).append("(at:").append(input.substring(index + 3)).append(") "); + input = taskStringBuilder.toString(); + Task task = new Task(TaskType.EVENT, false, input, tag); + taskList.add(task); + + outputStringBuilder.append("Got it. I've added this task:").append("\n"); + + outputStringBuilder.append(" ").append(task.toFullOutputString()).append("\n"); + + temp = "Now you have " + taskList.size() + " tasks in the list."; + outputStringBuilder.append("Now you have ").append(taskList.size()).append(" tasks in the list."); + + return outputStringBuilder.toString(); + } catch (duke.exception.DukeException e) { + return outputStringBuilder.append(e.getMessage()).toString(); + } + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/command/CommandFind.java b/src/main/java/command/CommandFind.java new file mode 100644 index 0000000000..0308e00461 --- /dev/null +++ b/src/main/java/command/CommandFind.java @@ -0,0 +1,42 @@ +package duke.command; + +import duke.task.TaskList; +import duke.task.Task; +import duke.ui.Ui; +import duke.storage.Storage; + +/** + * Find Command + * + * Search for task in tasklist and outputs the task and its number. + * Tasks displayed contains the phrase used while describing the command. + */ +public class CommandFind implements Command { + private final String fullCommand; + + public CommandFind(String fullCommand) { + this.fullCommand = fullCommand; + } + + @Override + public String execute(TaskList taskList, Ui ui, Storage storage) { + assert fullCommand.length() <= 6 : "find: string is too short."; + String phrase = fullCommand.substring(5); + StringBuilder outputStringBuilder = new StringBuilder(); + String temp = "Here are the matching tasks in your list:"; + outputStringBuilder.append(temp).append("\n"); + for (int taskListIndex = 1; taskListIndex <= taskList.size(); taskListIndex++) { + Task task = taskList.get(taskListIndex -1); + if (task.toFullOutputString().contains(phrase)) { + temp = taskListIndex + "." + task.toFullOutputString(); + outputStringBuilder.append(temp).append("\n"); + } + } + return outputStringBuilder.deleteCharAt(outputStringBuilder.length()-1).toString(); + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/command/CommandList.java b/src/main/java/command/CommandList.java new file mode 100644 index 0000000000..642631f613 --- /dev/null +++ b/src/main/java/command/CommandList.java @@ -0,0 +1,32 @@ +package duke.command; + +import duke.task.TaskList; +import duke.task.Task; +import duke.ui.Ui; +import duke.storage.Storage; + +/** + * List Command + * + * lists all the Tasks in TaskList. + */ + +public class CommandList implements Command{ + @Override + public String execute(TaskList taskList, Ui ui, Storage storage) { + Task task; + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("Here are the tasks in your list:").append("\n"); + for (int i = 1; i <= taskList.size(); i++) { + task = taskList.get(i - 1); + stringBuilder.append(i).append(". ").append(task.toFullOutputString()).append("\n"); + } + stringBuilder.deleteCharAt(stringBuilder.length()-1); + return stringBuilder.toString(); + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/command/CommandSave.java b/src/main/java/command/CommandSave.java new file mode 100644 index 0000000000..779e7f6210 --- /dev/null +++ b/src/main/java/command/CommandSave.java @@ -0,0 +1,24 @@ +package duke.command; + +import duke.task.TaskList; +import duke.ui.Ui; +import duke.storage.Storage; + +/** + * Save Command + * + * Saves the TaskList to the data file. + * Path name of the file is specified in duke's static variable PATH_NAME. + */ +public class CommandSave implements Command { + @Override + public String execute(TaskList taskList, Ui ui, Storage storage) { + String temp = storage.save(taskList); + return temp; + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/command/CommandTodo.java b/src/main/java/command/CommandTodo.java new file mode 100644 index 0000000000..94464652f6 --- /dev/null +++ b/src/main/java/command/CommandTodo.java @@ -0,0 +1,60 @@ +package duke.command; + +import duke.task.TaskList; +import duke.task.Task; +import duke.ui.Ui; +import duke.storage.Storage; +import duke.task.TaskType; +import duke.exception.DukeException; + +/** + * Todo Command + * + * creates a Task of type Todo and adds it to the taskList + */ + +public class CommandTodo implements Command{ + private final String fullCommand; + + public CommandTodo(String fullCommand) { + this.fullCommand = fullCommand; + } + + @Override + public String execute(TaskList taskList, Ui ui, Storage storage) { + StringBuilder outputStringBuilder = new StringBuilder(); + assert fullCommand.length() <= 5 : "todo: string is too short."; + String input = fullCommand.substring(4); + + try { + String[] inputAndTag = Task.getTagFromString(input); + input = inputAndTag[0]; + String tag = inputAndTag[1]; + + if (input.isEmpty()) { + outputStringBuilder.append("\uD83D\uDE00 OOPS!!! The description of a todo cannot be empty."); + return outputStringBuilder.toString(); + } + + Task task = new Task(TaskType.TODO, false, input, tag); + taskList.add(task); + + + outputStringBuilder.append("Got it. I've added this task:").append("\n"); + + outputStringBuilder.append(" ").append(task.toFullOutputString()).append("\n"); + + outputStringBuilder.append("Now you have ").append(taskList.size()).append(" tasks in the list."); + + return outputStringBuilder.toString(); + } catch (DukeException e) { + return outputStringBuilder.append(e.getMessage()).toString(); + } + + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/exception/DukeException.java b/src/main/java/exception/DukeException.java new file mode 100644 index 0000000000..62402c6312 --- /dev/null +++ b/src/main/java/exception/DukeException.java @@ -0,0 +1,10 @@ +package duke.exception; + +/** + * Exception class that gets thrown by supporting classes' methods. + */ +public class DukeException extends Exception { + public DukeException(String string) { + super(string); + } +} \ No newline at end of file diff --git a/src/main/java/gui/DialogBox.java b/src/main/java/gui/DialogBox.java new file mode 100644 index 0000000000..a4fed9446a --- /dev/null +++ b/src/main/java/gui/DialogBox.java @@ -0,0 +1,61 @@ +package duke.gui; + +import java.io.IOException; +import java.util.Collections; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; + +/** + * An example of a custom control using FXML. + * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label + * containing text from the speaker. + */ +public class DialogBox extends HBox { + @FXML + private Label dialog; + @FXML + private ImageView displayPicture; + + private DialogBox(String text, Image img) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + dialog.setText(text); + displayPicture.setImage(img); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + Collections.reverse(tmp); + getChildren().setAll(tmp); + setAlignment(Pos.TOP_LEFT); + } + + public static DialogBox getUserDialog(String text, Image img) { + return new DialogBox(text, img); + } + + public static DialogBox getDukeDialog(String text, Image img) { + var db = new DialogBox(text, img); + db.flip(); + return db; + } +} \ No newline at end of file diff --git a/src/main/java/gui/Launcher.java b/src/main/java/gui/Launcher.java new file mode 100644 index 0000000000..04f5c879ea --- /dev/null +++ b/src/main/java/gui/Launcher.java @@ -0,0 +1,13 @@ +package duke.gui; + +import javafx.application.Application; + +/** + * A launcher class to workaround classpath issues. + */ +public class Launcher { + + public static void main(String[] args) { + Application.launch(Main.class, args); + } +} diff --git a/src/main/java/gui/Main.java b/src/main/java/gui/Main.java new file mode 100644 index 0000000000..e6376882e2 --- /dev/null +++ b/src/main/java/gui/Main.java @@ -0,0 +1,33 @@ +package duke.gui; + +import duke.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.setScene(scene); + fxmlLoader.getController().setDuke(duke); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/gui/MainWindow.java b/src/main/java/gui/MainWindow.java new file mode 100644 index 0000000000..9333011345 --- /dev/null +++ b/src/main/java/gui/MainWindow.java @@ -0,0 +1,78 @@ +package duke.gui; + +import duke.Duke; + +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +/** + * 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/chickuser.png")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/secretarybird.png")); + + private boolean isExit = false; + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + } + + public void setDuke(Duke d) { + duke = d; + showWelcome(); + } + + @FXML + private void showWelcome() { + String response = duke.welcomeGui(); + dialogContainer.getChildren().add( + DialogBox.getDukeDialog(response, dukeImage) + ); + } + + /** + * 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() { + handleExit(); + String input = userInput.getText(); + String response = duke.getResponse(input); + if(input.equals("bye")) { + isExit = true; + } + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog("[in chick language]\n" + input, userImage), + DialogBox.getDukeDialog("[in bird language]\n" +response, dukeImage) + ); + userInput.clear(); + } + + @FXML + private void handleExit() { + if (isExit) { + Stage stage = (Stage) scrollPane.getScene().getWindow(); + stage.close(); + } + } +} diff --git a/src/main/java/parser/Parser.java b/src/main/java/parser/Parser.java new file mode 100644 index 0000000000..9e89cf7352 --- /dev/null +++ b/src/main/java/parser/Parser.java @@ -0,0 +1,62 @@ +package duke.parser; + +import duke.exception.DukeException; + +import duke.command.Command; +import duke.command.CommandList; +import duke.command.CommandBye; +import duke.command.CommandDeadline; +import duke.command.CommandDelete; +import duke.command.CommandDone; +import duke.command.CommandEvent; +import duke.command.CommandFind; +import duke.command.CommandSave; +import duke.command.CommandTodo; + + +/** + * Parser deals with the full commands given by the user. + * Inputs given are deciphered and corresponding responses are outputted. + */ + +public class Parser { + public static Command parse(String fullCommand) throws DukeException { + if(fullCommand.equals("list")) { + return new CommandList(); + } else if (fullCommand.equals("bye")) { + return new CommandBye(); + } else if (getWord(fullCommand).equals("done")) { + return new CommandDone(fullCommand); + } else if (getWord(fullCommand).equals("todo")) { + return new CommandTodo(fullCommand); + } else if (getWord(fullCommand).equals("deadline")) { + return new CommandDeadline(fullCommand); + } else if (getWord(fullCommand).equals("event")) { + return new CommandEvent(fullCommand); + } else if (getWord(fullCommand).equals("delete")) { + return new CommandDelete(fullCommand); + } else if(fullCommand.equals("save")) { + return new CommandSave(); + } else if (getWord(fullCommand).equals("find")) { + return new CommandFind(fullCommand); + } else{ + throw new DukeException("\uD83D\uDE2D OOPS!!! I'm sorry, but I don't know what that means :-("); + } + } + + /** + * Returns the first word in the string indicated by the first blank space. + * If there are no blank space, the entire string is returned. + * + * @param string + * @return + */ + public static String getWord(String string) { + + int firstSpaceIndex = string.indexOf(' '); + if (firstSpaceIndex == -1) { + return string; + } + return string.substring(0, firstSpaceIndex); + } +} diff --git a/src/main/java/storage/Storage.java b/src/main/java/storage/Storage.java new file mode 100644 index 0000000000..81a0d66aa2 --- /dev/null +++ b/src/main/java/storage/Storage.java @@ -0,0 +1,138 @@ +package duke.storage; + +import duke.exception.DukeException; +import duke.task.Task; +import duke.task.TaskList; +import duke.task.TaskType; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.FormatStyle; +import java.util.ArrayList; +import java.util.Optional; +import java.util.Scanner; + +/** + * Storage deals with loading and saving task list from/ to memory. + */ + +public class Storage { + private String filePath; + + public Storage(String filePath) { + this.filePath = filePath; + } + + public ArrayList load() throws DukeException { + File file = new File(filePath); + assert file.exists() : "File does not exist."; + ArrayList list = new ArrayList<>(); + try { + Scanner sc = new Scanner(file); //causing exception + String task, taskString, tag; + TaskType taskType; + boolean isDone; + LocalDateTime dateTime; + while (sc.hasNext()) { + task = sc.nextLine(); + switch (task.charAt((1))) { + case 'T' : + taskType = TaskType.TODO; + break; + case 'D' : + taskType = TaskType.DEADLINE; + break; + case 'E' : + taskType = TaskType.EVENT; + break; + default : + throw new DukeException("Oh no! TaskType in file is wrong. File is corrupted!"); + } + switch (task.charAt((4))) { + case '\u2713' : + isDone = true; + break; + case '\u2717' : + isDone = false; + break; + default : + throw new DukeException("Oh no! isDone in file is wrong. File is corrupted!"); + } + String[] stringAndTag = getTag(task); + taskString = stringAndTag[0]; + tag = stringAndTag[1]; + int hashIndex = task.lastIndexOf("#"); + taskString = task.substring(6,hashIndex); + + if (taskType == TaskType.DEADLINE) { + int index = task.indexOf("(by: "); + if (index == -1) { + throw new DukeException("Oh no! Deadline task does not have a deadline. File is corrupted!"); + } + try { + dateTime = LocalDateTime.parse(task.substring(index + 5, hashIndex - 2), DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT)); + } catch (DateTimeParseException e ) { + throw new DukeException("Oh no! File contains incorrect date format. File is corrupted!"); + } + list.add(new Task(taskType, isDone, taskString, Optional.of(dateTime), tag)); + } else { + list.add(new Task(taskType, isDone, taskString, tag)); + } + } + + } catch (FileNotFoundException e) { + throw new DukeException("File not found."); + } + return list; + } + + public String save(TaskList taskList) { + File file = new File(filePath); + assert file.exists() : "File does not exist."; + if(file.getParentFile() != null) { + file.getParentFile().mkdirs(); + } + try { + file.createNewFile(); + FileWriter fileWriter = new FileWriter(filePath); + Task task; + + for (int listIndex = 0; listIndex < taskList.size(); listIndex++) { + task = taskList.get(listIndex); + fileWriter.write(task.toFullFileString() + System.lineSeparator()); + } + fileWriter.close(); + + return "Tasks have been saved!"; + } catch (IOException e) { + return e.getMessage(); + } + } + + public String clear() { + save(new TaskList()); + return "Task file has been cleared!"; + } + + public String[] getTag(String task) throws DukeException { + int hashIndex = task.lastIndexOf("#"); + if(hashIndex == -1) { + throw new DukeException("No # found. File is corrupted."); + } + if (hashIndex == task.length()) { + throw new DukeException("No description for tag found. File is corrupted."); + } + String[] output = new String[2]; + output[0] = task.substring(0,hashIndex); + output[1] = task.substring(hashIndex + 1); + if (Task.isEmptyTag(output[1])) { + output[1] = ""; + } + return output; + } +} \ No newline at end of file diff --git a/src/main/java/task/Task.java b/src/main/java/task/Task.java new file mode 100644 index 0000000000..0105df4161 --- /dev/null +++ b/src/main/java/task/Task.java @@ -0,0 +1,134 @@ +package duke.task; + +import duke.exception.DukeException; + +import java.time.LocalDateTime; +import java.util.Optional; + +/** + * Task stores it type, a boolean indicating whether the task has been completed, a string describing the task and a + * time. + */ + +public class Task { + private TaskType taskType; + private boolean isDone; + private String string; + private Optional dateTime; + private String tag ; + + public Task(TaskType taskType, boolean isDone, String string) { + this.taskType = taskType; + this.isDone = isDone; + this.string = string; + this.dateTime = Optional.empty(); + this.tag = ""; + } + + public Task(TaskType taskType, boolean isDone, String string, Optional dateTime) { + this.taskType = taskType; + this.isDone = isDone; + this.string = string; + this.dateTime = dateTime; + this.tag = ""; + } + + public Task(TaskType taskType, boolean isDone, String string, String tag) { + this.taskType = taskType; + this.isDone = isDone; + this.string = string; + this.dateTime = Optional.empty(); + this.tag = tag; + } + + public Task(TaskType taskType, boolean isDone, String string, Optional dateTime, String tag) { + this.taskType = taskType; + this.isDone = isDone; + this.string = string; + this.dateTime = dateTime; + this.tag = tag; + } + + public String getString() { + return string; + } + + public String getDoneString() { + String string; + return (isDone) ? "[\u2713]" : "[\u2717]"; // ✓ or ✗ + } + + public Task done() { + return new Task(taskType, true, string, dateTime, tag); + } + + public String getTypeString() { + String string; + assert taskType != null : "taskType has not been instantiated."; + + if(taskType.equals(TaskType.TODO)) { + string = "[T]"; + } + else if(taskType.equals(TaskType.DEADLINE)) { + string = "[D]"; + } + else{ + string = "[E]"; + } + return string; + } + + public String toString() { + return string; + } + + public String toFullOutputString() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(getTypeString()).append(getDoneString()).append(toString()); + if(!tag.isEmpty()) { + stringBuilder.append("#").append(tag); + } + return stringBuilder.toString(); + } + + public String toFullFileString() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(getTypeString()).append(getDoneString()).append(toString()).append("#").append(getTag()); + return stringBuilder.toString(); + } + + public String getTag() { + return (tag.isEmpty()) ? "/n" : tag; + } + + + static public boolean isEmptyTag(String tag) { + return tag.equals("/n"); + } + + static public String[] getTagFromString(String fullCommand) throws DukeException { + String[] output = new String[2]; + int tagIndex = fullCommand.indexOf("/tag "); + if (tagIndex == -1) { + output[0] = fullCommand; + output[1] = ""; + return output; + } + if (fullCommand.length() <= tagIndex + 5) { + throw new DukeException("Oh no! Tag description cannot be empty! Remove \"/tag\" instead."); + } + String tag = fullCommand.substring(tagIndex + 5); + if (tag.equals("/n")) { + throw new DukeException("Oh no! The tag description: \"/n\" is a reserved tag. Please use another tag."); + } + + String[] tagSubString = tag.split(" "); + tag = tagSubString[0]; //Only use the first word after /tag + if (!tag.matches("[A-Za-z0-9]+")) { + throw new DukeException("Oh no! Tag needs to be alphanumeric!"); + } + output[0] = fullCommand.substring(0,tagIndex); + output[1] = tag; + return output; + } +} diff --git a/src/main/java/task/TaskList.java b/src/main/java/task/TaskList.java new file mode 100644 index 0000000000..2751a60955 --- /dev/null +++ b/src/main/java/task/TaskList.java @@ -0,0 +1,41 @@ +package duke.task; + +import java.util.ArrayList; + +/** + * TaskList contains an ArrayList of Task. + * It serves as a barrier to directly using the ArrayList class so as to limit the functions available to the client. + */ +public class TaskList { + private ArrayList tasks; + + public TaskList() { + tasks = new ArrayList<>(); + } + + public TaskList(ArrayList tasks) { + this.tasks = tasks; + } + + public int size() { + return tasks.size(); + } + + public Task get(int i) { + return tasks.get(i); + } + + public boolean add(Task task) { + tasks.add(task); + return true; + } + + public void add(int index, Task task){ + tasks.add(index, task); + } + + public Task remove(int index){ + return tasks.remove(index); + } + +} \ No newline at end of file diff --git a/src/main/java/task/TaskType.java b/src/main/java/task/TaskType.java new file mode 100644 index 0000000000..0ac596075b --- /dev/null +++ b/src/main/java/task/TaskType.java @@ -0,0 +1,10 @@ +package duke.task; + +/** + * TaskType is an enum that represents that types of tasks. + */ +public enum TaskType { + TODO, + DEADLINE, + EVENT; +} \ No newline at end of file diff --git a/src/main/java/ui/Ui.java b/src/main/java/ui/Ui.java new file mode 100644 index 0000000000..30aaffbc52 --- /dev/null +++ b/src/main/java/ui/Ui.java @@ -0,0 +1,48 @@ +package duke.ui; + +import java.util.Scanner; + +/** + * Ui deals with any interaction with the user. + * This includes both user inputs and outputs to user. + */ + +public class Ui { + + public void showWelcome() { + System.out.println(welcome()); + } + + public String welcome() { + String logo = " ____ _ \n" + + "| _ \\ _ _| | _____ \n" + + "| | | | | | | |/ / _ \\\n" + + "| |_| | |_| | < __/\n" + + "|____/ \\__,_|_|\\_\\___|\n" + + " Hello I'm Duke\n" + + " What can I do for you?"; + + return logo; + } + + public String readCommand() { + Scanner scanner = new Scanner(System.in); + return scanner.nextLine(); + } + + public void showLoadingError() { + System.out.println("No storage file found."); + } + + public void showLine() { + System.out.println("____________________________________________________________\n"); + } + + public void showError(String string) { + System.out.println(string); + } + + public void showOutput(String string){ + System.out.println(string); + } +} \ No newline at end of file diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png new file mode 100644 index 0000000000..d893658717 Binary files /dev/null and b/src/main/resources/images/DaDuke.png differ diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png new file mode 100644 index 0000000000..3c82f45461 Binary files /dev/null and b/src/main/resources/images/DaUser.png differ diff --git a/src/main/resources/images/chickuser.png b/src/main/resources/images/chickuser.png new file mode 100644 index 0000000000..a09f62a14e Binary files /dev/null and b/src/main/resources/images/chickuser.png differ diff --git a/src/main/resources/images/secretarybird.png b/src/main/resources/images/secretarybird.png new file mode 100644 index 0000000000..af42bdf093 Binary files /dev/null and b/src/main/resources/images/secretarybird.png differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..a77affcfa7 --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..de92974e28 --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + +