diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..313b6f2ec7 --- /dev/null +++ b/build.gradle @@ -0,0 +1,70 @@ +plugins { + id 'java' + id 'application' + id 'checkstyle' + id 'com.github.johnrengelman.shadow' version '5.1.0' +} + +repositories { + mavenCentral() +} + +dependencies { + 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' + + 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 = "Launcher" +} + +shadowJar { + archiveBaseName = "duke" + archiveClassifier = null +} + +checkstyle { + toolVersion = '8.29' +} + +run{ + standardInput = System.in +} + +jar { + manifest { + attributes( + + 'Main-Class': 'Launcher' + ) + } +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..7951f3a6c4 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,398 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..135ea49ee0 --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/data.txt b/data.txt new file mode 100644 index 0000000000..227d12922d --- /dev/null +++ b/data.txt @@ -0,0 +1,5 @@ +T;0;readbook +D;0;return book ;2/12/2019 1800 +T;0;week 4 ip +T;1;sleep +E;0;nus run ;26/9/2021 1700 diff --git a/docs/README.md b/docs/README.md index 8077118ebe..0a2870c380 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,177 @@ # User Guide +UI Screenshot + +Duke is a task management desktop application that provides an effective +and efficient way to manage all your tasks. ## Features -### Feature-ABC +### 1. Simplistic Graphical User Interface + +Duke features a simple and elegant chat-like interface where you can type your +commands into a textbox and hit send, and you will hear back from Duke. + +### 2. Adding and managing tasks -Description of the feature. +There are 3 types of tasks you can add in Duke: +1) **Todo** tasks that consist of a description +2) **Event** tasks that consist of a description and a date +3) **Deadline** tasks that consist of a description and a date -### Feature-XYZ +You can mark a task as done once you have completed it, and you can also +delete a task if you no longer want it on your list. -Description of the feature. +You can also search for tasks in your list based on keywords. + +Remember to enter `bye` to save your changes and exit the app! ## Usage -### `Keyword` - Describe action +### A. Adding New Tasks + +You can add 3 different types of tasks. + +1. **Adding a todo tasks:** + +```todo {description}``` + +For example: + +```todo read book``` + +2. **Adding an event task:** -Describe the action and its outcome. +```event {description} /at {date} ``` -Example of usage: +The date can be purely a date or a date with a time. It should be +in one of the following formats: +- dd/MM/yyyy ```2/12/2019``` +- dd/MM/yyyy HHmm ```2/12/2019 1800``` -`keyword (optional arguments)` +For example: + +```event math exam /at 2/12/2019 1800``` + +```event math exam /at 2/12/2019``` + +3. **Adding a deadline task:** + +```deadline {description} /by {date} ``` + +For example: + +```deadline return book /by 2/12/2019 1800``` + +```deadline return book /by 2/12/2019``` + + + +Expected outcome of adding tasks: +``` +todo read book +``` + +``` +Got it. I've added this task: + [T][] read book +``` +### B. Viewing all tasks + +Type ```list``` to view a list of all your tasks Expected outcome: -Description of the outcome. +``` +list +``` ``` -expected output +1.[T][] read book +2.[D][] return book (by: Mon, Dec 02 2019, 18:00) +3.[E][] math exam (at: Mon, Dec 02 2019, 18:00) ``` + +### C. Marking task as done + +By seeing the output of ```list```, obtain the number of the task that +you want to mark as done. Let's say this is task number 1. + +Type ```done 1``` to mark the 1st task in the list as done. + +Expected outcome: + +``` +done 1 +``` +``` +Nice! I've marked this task as done: +[T][X] read book +``` + +``` +list +``` +``` +1.[T][X] read book +2.[D][] return book (by: Mon, Dec 02 2019, 18:00) +3.[E][] math exam (at: Mon, Dec 02 2019, 18:00) +``` + +### D. Deleting tasks + +By seeing the output of ```list```, obtain the number of the task that +you want to delete. Let's say this is task number 1. + +Type ```delete 1``` to delete the 1st task in the list. + +Expected outcome: + +``` +delete 1 +``` +``` +Noted. I've removed this task: +[T][X] read book +``` + +``` +list +``` +``` +1.[D][] return book (by: Mon, Dec 02 2019, 18:00) +2.[E][] math exam (at: Mon, Dec 02 2019, 18:00) +``` + +### E. Searching for matching tasks + +Type ```find {words}``` to search for task descriptions that match the word(s) or letters +that you have keyed in. + +Example of usage: + +`find book` + +Expected outcome: + +``` +find book +``` + +``` +Here are the matching tasks: +1.[D][] return book (by: Mon, Dec 02 2019, 18:00) +``` + +``` +find t +``` + +``` +Here are the matching tasks: +1.[D][] return book (by: Mon, Dec 02 2019, 18:00) +2.[E][] math exam (at: Mon, Dec 02 2019, 18:00) +``` + +### Acknowledgements: +- Original Duke template forked from Prof Damith CS2103T github +- GUI Design based on Javafx tutorials diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..45cba2109b 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..b849713594 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-leap-day \ 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 index 5d313334cc..bd04e33c10 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,10 +1,44 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); +import java.io.FileNotFoundException; + +import duke.utils.Parser; +import duke.utils.Storage; +import duke.utils.TaskList; +import duke.utils.Ui; +import javafx.application.Application; +import javafx.stage.Stage; + + +/** + * Command Line Task Manager called Duke + * + * @author Rama Venkatesh + */ + +public class Duke extends Application { + + private TaskList taskList; + private Ui ui; + private Parser parser; + private Storage storage; + + /** + * Constructor that instantiates Duke + */ + public Duke() { + parser = new Parser(); + this.storage = new Storage(); + try { + taskList = storage.loadTaskList(); + } catch (FileNotFoundException e) { + System.out.println(e.getMessage()); + } } + + @Override + public void start(Stage stage) { + ui = new Ui(stage, taskList, storage, parser); + ui.init(); + } + } + diff --git a/src/main/java/Launcher.java b/src/main/java/Launcher.java new file mode 100644 index 0000000000..eb292faf89 --- /dev/null +++ b/src/main/java/Launcher.java @@ -0,0 +1,7 @@ +import javafx.application.Application; + +public class Launcher { + public static void main(String[] args) { + Application.launch(Duke.class, args); + } +} diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..8a51453653 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: Launcher + diff --git a/src/main/java/duke/commands/Command.java b/src/main/java/duke/commands/Command.java new file mode 100644 index 0000000000..be013473b2 --- /dev/null +++ b/src/main/java/duke/commands/Command.java @@ -0,0 +1,32 @@ +package duke.commands; + +import java.io.IOException; + +import duke.exceptions.DukeException; +import duke.utils.Storage; +import duke.utils.TaskList; +import duke.utils.Ui; + + +/** + * Abstract class that represents a Command in the application + */ +public abstract class Command { + /** + * Executes the command's behaviour by performing the steps specified. + * + * @param taskList TaskList object that is being used in the app + * @param ui the Ui object that is being used in the app + * @param storage the Storage object that is being used in the app + * @throws DukeException + */ + public abstract void execute(TaskList taskList, Ui ui, Storage storage) throws DukeException, IOException; + + /** + * Returns whether or not this command is an exit command + * + * @return boolean whether this is an exit command + */ + public abstract boolean isExit(); + +} diff --git a/src/main/java/duke/commands/DeadlineCommand.java b/src/main/java/duke/commands/DeadlineCommand.java new file mode 100644 index 0000000000..7568654e74 --- /dev/null +++ b/src/main/java/duke/commands/DeadlineCommand.java @@ -0,0 +1,42 @@ +package duke.commands; + +import duke.tasks.DeadlineTask; +import duke.utils.Storage; +import duke.utils.TaskList; +import duke.utils.Ui; + +/** + * Class that is a subclass of Command class + * and handles the behaviour of the Command for deadline tasks + */ +public class DeadlineCommand extends Command { + + private String commandString; + + public DeadlineCommand(String commandString) { + this.commandString = commandString; + } + + @Override + public void execute(TaskList taskList, Ui ui, Storage storage) { + String[] dateArray = commandString.split("/"); + String date = ""; + for (int i = 1; i < dateArray.length; i++) { + if (i == 1) { + date += dateArray[1].substring(3); + } else { + date += "/" + dateArray[i]; + } + } + DeadlineTask newDeadline = new DeadlineTask(commandString.substring(9).split("/")[0], date); + taskList.addTask(newDeadline); + ui.printResponse("Got it. I've added this task: "); + ui.printResponse(" " + newDeadline.toString()); + + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/duke/commands/DeleteCommand.java b/src/main/java/duke/commands/DeleteCommand.java new file mode 100644 index 0000000000..71158023af --- /dev/null +++ b/src/main/java/duke/commands/DeleteCommand.java @@ -0,0 +1,49 @@ +package duke.commands; + +import duke.tasks.Task; +import duke.utils.Storage; +import duke.utils.TaskList; +import duke.utils.Ui; + +/** + * Class that is a subclass of Command class + * and handles the behaviour of the Command for deleting a task + */ +public class DeleteCommand extends Command { + + + private String commandString; + + public DeleteCommand(String commandString) { + this.commandString = commandString; + } + + @Override + public void execute(TaskList taskList, Ui ui, Storage storage) { + + String[] commandArr = commandString.split(" "); + try { + int taskNumberToDelete = Integer.parseInt(commandArr[1]); + if (taskNumberToDelete > taskList.numberOfTasks()) { + ui.printResponse("Invalid number"); + return; + } else { + int taskToDeleteIndex = taskNumberToDelete - 1; + Task tasktoDel = taskList.getTask(taskToDeleteIndex); + taskList.removeTask(taskToDeleteIndex); + ui.printResponse("Noted. I've removed this task: "); + ui.printResponse(" " + tasktoDel.toString()); + + } + } catch (Exception e) { + ui.printResponse("Invalid input"); + } + + } + + @Override + public boolean isExit() { + return false; + } + +} diff --git a/src/main/java/duke/commands/DoneCommand.java b/src/main/java/duke/commands/DoneCommand.java new file mode 100644 index 0000000000..d9d71cc9ab --- /dev/null +++ b/src/main/java/duke/commands/DoneCommand.java @@ -0,0 +1,45 @@ +package duke.commands; + +import duke.utils.Storage; +import duke.utils.TaskList; +import duke.utils.Ui; + +/** + * Class that is a subclass of Command class + * and handles the behaviour of the Command for + * marking a task as done + */ +public class DoneCommand extends Command { + + private String commandString; + + public DoneCommand(String commandString) { + this.commandString = commandString; + } + + @Override + public void execute(TaskList taskList, Ui ui, Storage storage) { + + String[] commandArr = commandString.split(" "); + try { + int taskNumberDone = Integer.parseInt(commandArr[1]); + if (taskNumberDone > taskList.numberOfTasks()) { + ui.printResponse("Invalid number"); + return; + } else { + int taskDoneIndex = taskNumberDone - 1; + taskList.getTask(taskDoneIndex).makeDone(); + ui.printResponse("Nice! I've marked this task as done: "); + ui.printResponse(taskList.getTask(taskDoneIndex).toString()); + } + } catch (Exception e) { + ui.printResponse("Invalid input"); + } + + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/duke/commands/ErrorCommand.java b/src/main/java/duke/commands/ErrorCommand.java new file mode 100644 index 0000000000..c09abef447 --- /dev/null +++ b/src/main/java/duke/commands/ErrorCommand.java @@ -0,0 +1,24 @@ +package duke.commands; + +import duke.utils.Storage; +import duke.utils.TaskList; +import duke.utils.Ui; + +/** + * Class that is a subclass of Command class + * and handles the behaviour of the Command for + * when an error is encountered + */ +public class ErrorCommand extends Command { + + @Override + public void execute(TaskList taskList, Ui ui, Storage storage) { + ui.printResponse("Invalid input"); + } + + @Override + public boolean isExit() { + return false; + } + +} diff --git a/src/main/java/duke/commands/EventCommand.java b/src/main/java/duke/commands/EventCommand.java new file mode 100644 index 0000000000..0d774aa922 --- /dev/null +++ b/src/main/java/duke/commands/EventCommand.java @@ -0,0 +1,42 @@ +package duke.commands; + +import duke.tasks.EventTask; +import duke.utils.Storage; +import duke.utils.TaskList; +import duke.utils.Ui; + +/** + * Class that is a subclass of Command class + * and handles the behaviour of the Command for event tasks + */ +public class EventCommand extends Command { + private String commandString; + + public EventCommand(String commandString) { + this.commandString = commandString; + } + + @Override + public void execute(TaskList taskList, Ui ui, Storage storage) { + + String[] dateArray = commandString.split("/"); + String date = ""; + for (int i = 1; i < dateArray.length; i++) { + if (i == 1) { + date += dateArray[1].substring(3); + } else { + date += "/" + dateArray[i]; + } + } + + EventTask newEvent = new EventTask(commandString.substring(6).split("/")[0], date); + taskList.addTask(newEvent); + ui.printResponse("Got it. I've added this task: "); + ui.printResponse(" " + newEvent.toString()); + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/duke/commands/ExitCommand.java b/src/main/java/duke/commands/ExitCommand.java new file mode 100644 index 0000000000..23d4a3b780 --- /dev/null +++ b/src/main/java/duke/commands/ExitCommand.java @@ -0,0 +1,26 @@ +package duke.commands; + +import java.io.IOException; + +import duke.utils.Storage; +import duke.utils.TaskList; +import duke.utils.Ui; + +/** + * Class that is a subclass of Command class + * and handles the behaviour of the Command for + * when exiting the program + */ +public class ExitCommand extends Command { + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws IOException { + storage.saveTaskListToDisk(tasks); + ui.end(); + } + + @Override + public boolean isExit() { + return true; + } + +} diff --git a/src/main/java/duke/commands/FindCommand.java b/src/main/java/duke/commands/FindCommand.java new file mode 100644 index 0000000000..b8b0468095 --- /dev/null +++ b/src/main/java/duke/commands/FindCommand.java @@ -0,0 +1,57 @@ +package duke.commands; + +import duke.exceptions.DukeException; +import duke.utils.Storage; +import duke.utils.TaskList; +import duke.utils.Ui; + +/** + * Class that is a subclass of Command class + * and handles the behaviour of the Command for + * finding tasks with keywords + */ +public class FindCommand extends Command { + + private String commandString; + + public FindCommand(String commandString) { + this.commandString = commandString; + } + + @Override + public void execute(TaskList taskList, Ui ui, Storage storage) throws DukeException { + String[] commandArr = commandString.split(" "); + boolean anyMatchFound = false; + int numberOfMatches = 0; + + for (int i = 0; i < taskList.numberOfTasks(); i++) { + boolean currentTaskHasBeenPrinted = false; + + // loop again for each keyword in input + for (int word = 1; word < commandArr.length; word++) { + boolean matches = taskList.getTask(i).getName().toLowerCase().contains(commandArr[word].toLowerCase()); + if (matches) { + if (!anyMatchFound) { + ui.printResponse("Here are the matching tasks:"); + } + anyMatchFound = true; + numberOfMatches++; + if (!currentTaskHasBeenPrinted) { + ui.printResponse((numberOfMatches) + "." + taskList.getTask(i).toString()); + currentTaskHasBeenPrinted = true; + } + + } + } + } + if (!anyMatchFound) { + ui.printResponse("Sorry, we are unable to find any matching tasks"); + } + + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/duke/commands/HelpCommand.java b/src/main/java/duke/commands/HelpCommand.java new file mode 100644 index 0000000000..fc90f48f81 --- /dev/null +++ b/src/main/java/duke/commands/HelpCommand.java @@ -0,0 +1,50 @@ +package duke.commands; + +import duke.exceptions.DukeException; +import duke.utils.Storage; +import duke.utils.TaskList; +import duke.utils.Ui; + +/** + * Class that is a subclass of Command class + * and handles the behaviour of the Command for + * when the user requests for help. + */ +public class HelpCommand extends Command { + + /** + * Gives a list of possible commands that user can type. + * @param taskList TaskList object that is being used in the app + * @param ui the Ui object that is being used in the app + * @param storage the Storage object that is being used in the app + * @throws DukeException + */ + public void execute(TaskList taskList, Ui ui, Storage storage) throws DukeException { + ui.printResponse("Here are the different commands in Duke"); + ui.printResponse("Type 'bye' to exit."); + ui.printResponse("Type 'list' to view all tasks in your list."); + ui.printResponse("Type 'delete x' to delete task number x. Eg. 'delete 5'"); + ui.printResponse("Type 'done x' to mark task number x as done. Eg. 'done 5'"); + ui.printResponse("Type 'find wordtofind' to search for tasks that contain the word"); + ui.printResponse("Eg. 'find apple' "); + + ui.printResponse("There are 3 types of tasks: Todo, Event, and Deadline"); + ui.printResponse("To add a Todo task, type 'todo' followed by your task description"); + ui.printResponse("Eg. 'todo read book' "); + ui.printResponse("To add a Event task, type 'event' followed by your task description, " + + + "followed by '/at' followed by your event date"); + ui.printResponse("Eg. 'event finance paper /at 2/12/2019 1800' "); + ui.printResponse("To add a Deadline task, type 'event' followed by your task description, " + + + "followed by '/by' followed by your deadline "); + ui.printResponse("Eg. 'deadline return book /by 2/12/2019 1800' "); + + ui.printResponse("Type 'help' to see the list of commands available"); + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/duke/commands/ListCommand.java b/src/main/java/duke/commands/ListCommand.java new file mode 100644 index 0000000000..8afcaa6b98 --- /dev/null +++ b/src/main/java/duke/commands/ListCommand.java @@ -0,0 +1,26 @@ +package duke.commands; + +import duke.utils.Storage; +import duke.utils.TaskList; +import duke.utils.Ui; + +/** + * Class that is a subclass of Command class + * and handles the behaviour of the Command for returning + * a list of the Tasks currently in the TaskList + */ +public class ListCommand extends Command { + + @Override + public void execute(TaskList taskList, Ui ui, Storage storage) { + for (int i = 0; i < taskList.numberOfTasks(); i++) { + ui.printResponse((i + 1) + "." + taskList.getTask(i).toString()); + } + } + + @Override + public boolean isExit() { + return false; + } + +} diff --git a/src/main/java/duke/commands/TodoCommand.java b/src/main/java/duke/commands/TodoCommand.java new file mode 100644 index 0000000000..3c6090eff3 --- /dev/null +++ b/src/main/java/duke/commands/TodoCommand.java @@ -0,0 +1,34 @@ +package duke.commands; + +import duke.tasks.TodoTask; +import duke.utils.Storage; +import duke.utils.TaskList; +import duke.utils.Ui; + + +/** + * Class that is a subclass of Command class + * and handles the behaviour of the Command for todotasks + */ +public class TodoCommand extends Command { + + private String commandString; + + public TodoCommand(String commandString) { + this.commandString = commandString; + } + + @Override + public void execute(TaskList taskList, Ui ui, Storage storage) { + TodoTask newTodo = new TodoTask(commandString.substring(5)); + taskList.addTask(newTodo); + ui.printResponse("Got it. I've added this task: "); + ui.printResponse(" " + newTodo.toString()); + } + + @Override + public boolean isExit() { + return false; + } + +} diff --git a/src/main/java/duke/exceptions/DukeException.java b/src/main/java/duke/exceptions/DukeException.java new file mode 100644 index 0000000000..42699f7c68 --- /dev/null +++ b/src/main/java/duke/exceptions/DukeException.java @@ -0,0 +1,14 @@ +package duke.exceptions; + +/** + * Class that handles exceptions specific to Duke + */ +public class DukeException extends Exception { + /** + * Constructor that initalizes a DukeException object. + * @param message + */ + public DukeException(String message){ + super(message); + } +} diff --git a/src/main/java/duke/tasks/DeadlineTask.java b/src/main/java/duke/tasks/DeadlineTask.java new file mode 100644 index 0000000000..023033c9fc --- /dev/null +++ b/src/main/java/duke/tasks/DeadlineTask.java @@ -0,0 +1,47 @@ +package duke.tasks; + +import java.util.Date; + +import duke.utils.DukeDate; + +/** + * Class for deadline tasks that contain a + * date or datetime + */ +public class DeadlineTask extends Task { + private String dateLiteral; + private Date dateFormatted; + private String dateReadable; + + /** + * Constructor that initializes an EventTask object + * @param description description of event + * @param by date of deadline + */ + public DeadlineTask(String description, String by) { + super(description, TaskType.DEADLINE); + this.dateLiteral = by; + this.dateFormatted = DukeDate.formatDate(by); + this.dateReadable = DukeDate.parseDateToString(dateFormatted); + } + + /** + * Gives the date associated to this task, in the same format + * as it was keyed in by the user in the CLI. + * + * @return String date + */ + public String getDate() { + return dateLiteral; + } + + @Override + public String toString() { + String typeString = type == TaskType.TODO ? "T" : type == TaskType.EVENT ? "E" : "D"; + String doneSymbol = isDone ? "X" : " "; + String result = "[" + typeString + "] " + "[" + doneSymbol + "] " + name + "(by: " + dateReadable + ")"; + + return result; + } + +} diff --git a/src/main/java/duke/tasks/EventTask.java b/src/main/java/duke/tasks/EventTask.java new file mode 100644 index 0000000000..6e4dab5ffb --- /dev/null +++ b/src/main/java/duke/tasks/EventTask.java @@ -0,0 +1,46 @@ +package duke.tasks; +import java.util.Date; + +import duke.utils.DukeDate; + +/** + * Class for event tasks that contain a + * date or datetime + */ +public class EventTask extends Task { + private String dateLiteral; + private Date dateFormatted; + private String dateReadable; + + /** + * Constructor that initializes an EventTask object + * @param description description of event + * @param at date of event + */ + public EventTask(String description, String at) { + super(description, TaskType.EVENT); + assert (!at.equals("")); + this.dateLiteral = at; + this.dateFormatted = DukeDate.formatDate(at); + this.dateReadable = DukeDate.parseDateToString(dateFormatted); + } + + /** + * Gives the date associated to this task, in the same format + * as it was keyed in by the user in the CLI. + * + * @return String date + */ + public String getDate() { + return dateLiteral; + } + + @Override + public String toString() { + String typeString = type == TaskType.TODO ? "T" : type == TaskType.EVENT ? "E" : "D"; + String doneSymbol = isDone ? "X" : " "; + String result = "[" + typeString + "] " + "[" + doneSymbol + "] " + name + "(at: " + dateReadable + ")"; + + return result; + } +} diff --git a/src/main/java/duke/tasks/Task.java b/src/main/java/duke/tasks/Task.java new file mode 100644 index 0000000000..ceb1ea5be1 --- /dev/null +++ b/src/main/java/duke/tasks/Task.java @@ -0,0 +1,70 @@ +package duke.tasks; + +/** + * Class that encapsulates a task and its information + */ +public class Task { + + protected TaskType type; + protected String name; + protected boolean isDone = false; + + public enum TaskType { + TODO, DEADLINE, EVENT + } + + /** + * Constructor that initializes a Task object + * @param name name of task + * @param type type of task (TODO, DEADLINE, EVENT) + */ + public Task(String name, TaskType type) { + this.name = name; + this.type = type; + } + + /** + * Method that returns whether or not task is done + * + * @return boolean whether or not task is done + */ + public boolean isDone() { + return isDone; + } + + /** + * Method that returns the task type of the task + * + * @return TaskType task's type + */ + public TaskType getType() { + return type; + } + + /** + * Method that returns the name of the task + * + * @return String name of task + */ + public String getName() { + return name; + } + + /** + * Method that marks the task as done by + * changing the Task's boolean variable isDone + * to true. + */ + public void makeDone() { + isDone = true; + } + + @Override + public String toString() { + String typeString = type == TaskType.TODO ? "T" : type == TaskType.EVENT ? "E" : "D"; + String doneSymbol = isDone ? "X" : " "; + String result = "[" + typeString + "] " + "[" + doneSymbol + "] " + name; + return result; + } + +} diff --git a/src/main/java/duke/tasks/TodoTask.java b/src/main/java/duke/tasks/TodoTask.java new file mode 100644 index 0000000000..68a3f7c9b8 --- /dev/null +++ b/src/main/java/duke/tasks/TodoTask.java @@ -0,0 +1,17 @@ +package duke.tasks; + +/** + * Class for deadline tasks that do not contain a + * date or datetime + */ +public class TodoTask extends Task { + + /** + * Constructor that initializes an EventTask object + * @param description description of event + */ + public TodoTask(String description) { + super(description, TaskType.TODO); + } + +} diff --git a/src/main/java/duke/utils/DialogBox.java b/src/main/java/duke/utils/DialogBox.java new file mode 100644 index 0000000000..9e10d57840 --- /dev/null +++ b/src/main/java/duke/utils/DialogBox.java @@ -0,0 +1,71 @@ +package duke.utils; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; + + +public class DialogBox extends HBox { + private Label text; + private ImageView displayPicture; + + /** + * Constructor that initializes a dialogbox with a label and an image + * @param l text that will be in the box + * @param iv image that will be next to the text + */ + public DialogBox(Label l, ImageView iv) { + text = l; + displayPicture = iv; + + text.setWrapText(true); + displayPicture.setFitWidth(100.0); + displayPicture.setFitHeight(100.0); + + this.setAlignment(Pos.TOP_RIGHT); + this.getChildren().addAll(text, displayPicture); + } + + /** + * Constructor that initializes a dialogbox with a label only + * @param l text that will be in the box + */ + public DialogBox(Label l) { + text = l; + + text.setWrapText(true); + this.setAlignment(Pos.TOP_RIGHT); + this.getChildren().addAll(text); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + this.setAlignment(Pos.TOP_LEFT); + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + FXCollections.reverse(tmp); + this.getChildren().setAll(tmp); + } + + public static DialogBox getUserDialog(Label l, ImageView iv) { + return new DialogBox(l, iv); + } + + public static DialogBox getDukeDialog(Label l, ImageView iv) { + var db = new DialogBox(l, iv); + db.flip(); + return db; + } + + public static DialogBox getDukeDialog(Label l) { + var db = new DialogBox(l); + db.flip(); + return db; + } + +} diff --git a/src/main/java/duke/utils/DukeDate.java b/src/main/java/duke/utils/DukeDate.java new file mode 100644 index 0000000000..3c09097cf5 --- /dev/null +++ b/src/main/java/duke/utils/DukeDate.java @@ -0,0 +1,61 @@ +package duke.utils; + +import java.text.SimpleDateFormat; +import java.util.Date; + + +/** + * Class that handles formatting the input date + */ +public class DukeDate { + + /** + * Formats the input datetime that was entered in dd/MM/YYYY HHMM + * format into a Date object + * + * @param dateLiteral date that was typed in by user + * @return Date object + */ + public static Date formatDate(String dateLiteral) { + if (dateLiteral.length() == 14 || dateLiteral.length() == 15) { + SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy HHmm"); + try { + Date dateFormatted = format.parse(dateLiteral); + return dateFormatted; + } catch (java.text.ParseException e) { + System.out.println("Incorrect date format"); + } + + } else if (dateLiteral.length() == 9 || dateLiteral.length() == 10) { + SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy"); + try { + Date dateFormatted = format.parse(dateLiteral); + return dateFormatted; + } catch (java.text.ParseException e) { + System.out.println("Incorrect date format"); + } + } + System.out.println("Incorrect date format"); + return new Date(); + + } + + /** + * Parses a Date object and converts it into a reader-friendly format + * @param date Date object + * @return String dateStr + */ + public static String parseDateToString(Date date) { + String[] dateArr = date.toString().split(" "); + String dateStr = ""; + dateStr += dateArr[0] + ", " + dateArr[1] + " " + dateArr[2] + " " + dateArr[5]; + + // for adding the time to the string + dateStr += ", " + dateArr[3].substring(0, 5); + + return dateStr; + } + + + +} diff --git a/src/main/java/duke/utils/Parser.java b/src/main/java/duke/utils/Parser.java new file mode 100644 index 0000000000..af6b4bb26f --- /dev/null +++ b/src/main/java/duke/utils/Parser.java @@ -0,0 +1,65 @@ +package duke.utils; + +import duke.commands.Command; +import duke.commands.DeadlineCommand; +import duke.commands.DeleteCommand; +import duke.commands.DoneCommand; +import duke.commands.ErrorCommand; +import duke.commands.EventCommand; +import duke.commands.ExitCommand; +import duke.commands.FindCommand; +import duke.commands.HelpCommand; +import duke.commands.ListCommand; +import duke.commands.TodoCommand; + +/** + * Class that parses user input and perfoms the corresponding + * function dictated by user input + */ +public class Parser { + + /** + * Parses the user input and returns the corresponding Command + * + * @param fullCommand the user's input into the CLI + * @return Command command to be executed + */ + public Command parseInput(String fullCommand) { + + String[] commandSplit = fullCommand.split(" "); + String actionWord = commandSplit[0]; + assert (actionWord != null); + + switch (actionWord) { + case "bye": + return new ExitCommand(); + + case "list": + return new ListCommand(); + + case "todo": + return new TodoCommand(fullCommand); + + case "deadline": + return new DeadlineCommand(fullCommand); + + case "event": + return new EventCommand(fullCommand); + + case "done": + return new DoneCommand(fullCommand); + + case "delete": + return new DeleteCommand(fullCommand); + + case "find": + return new FindCommand(fullCommand); + + case "help": + return new HelpCommand(); + + default: + return new ErrorCommand(); + } + } +} diff --git a/src/main/java/duke/utils/Storage.java b/src/main/java/duke/utils/Storage.java new file mode 100644 index 0000000000..53cc635260 --- /dev/null +++ b/src/main/java/duke/utils/Storage.java @@ -0,0 +1,196 @@ +package duke.utils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Scanner; + +import duke.tasks.DeadlineTask; +import duke.tasks.EventTask; +import duke.tasks.Task; +import duke.tasks.TodoTask; + + + +/** + * Class that handles storage of data by writing to disk + * and reading from disk + */ + +public class Storage { + + private static final String DELIMITER = ";"; + private String userDir = System.getProperty("user.dir"); + private String dataFilePath = userDir + "/" + "data.txt"; + + + /** + * Loads task list as a TaskList object from the data.txt file + * in the directory, and creates a new file if it does not + * already exist. + * + * @return TaskList containing tasks + * @throws FileNotFoundException + */ + public TaskList loadTaskList() throws FileNotFoundException { + + assert(dataFilePath != null); + + File f = new File(dataFilePath); + ArrayList taskList = new ArrayList(); + assert(taskList != null); + + if (!f.exists()) { + return new TaskList(new ArrayList()); + } + + try { + Scanner dataReader = new Scanner(f); + + while (dataReader.hasNextLine()) { + // convert saved info to Task + String currLine = dataReader.nextLine(); + Task currTask = convertStringToTask(currLine); + taskList.add(currTask); + } + dataReader.close(); + + } catch (FileNotFoundException e) { + System.out.println("File unable to load"); + } + + return new TaskList(taskList); + + } + + /** + * Saves the tasks data from the TaskList into the + * data.txt file in the directory. + * + * @param taskList tasklist + * @throws IOException + */ + public void saveTaskListToDisk(TaskList taskList) throws IOException { + assert (dataFilePath != null); + File f = new File(dataFilePath); + if (!f.exists()) { + try { + boolean created = f.createNewFile(); + } catch (IOException e) { + System.out.println("Error creating file"); + } + } + + try { + FileWriter writeTasks = new FileWriter(dataFilePath); + String allTasksString = ""; + + for (int i = 0; i < taskList.numberOfTasks(); i++) { + Task currTask = taskList.getTask(i); + String taskString = convertTaskToString(currTask); + allTasksString += taskString + "\n"; + System.out.println("written" + taskString); + + } + + writeTasks.write(allTasksString); + writeTasks.flush(); + writeTasks.close(); + System.out.println("I have saved your tasks."); + + + + } catch (IOException e) { + System.out.println("Error creating file"); + } + + } + + /** + * Converts a Task object to its String representation + * in an acceptable format to be stored in the data.txt file. + * + * @param task + * @return String representation of the task + */ + public String convertTaskToString(Task task) { + // Example storage in file: + // T;0;readbook + // D;0;return book ;2/12/2019 1800 + assert (task != null); + + String taskType = task.getType() == Task.TaskType.TODO + ? "T" + : task.getType() == Task.TaskType.EVENT + ? "E" + : "D"; + + String taskName = task.getName(); + + String isDone = task.isDone() ? "1" : "0"; + + String taskString = taskType + DELIMITER + isDone + DELIMITER + taskName; + + if (task.getType() == Task.TaskType.EVENT) { + taskString += DELIMITER; + taskString += ((EventTask) task).getDate(); + } + + if (task.getType() == Task.TaskType.DEADLINE) { + taskString += DELIMITER; + taskString += ((DeadlineTask) task).getDate(); + } + + return taskString; + + } + + /** + * Converts a task from the String format that is stored in the + * data.txt file into a Task Object. + * + * @param line String representation of a task + * @return + */ + public Task convertStringToTask(String line) { + // Example storage in file: + // T;0;readbook + // D;0;return book ;2/12/2019 1800 + String[] lineSplit = line.split(DELIMITER); + Task.TaskType taskType = lineSplit[0].equals("T") + ? Task.TaskType.TODO + : lineSplit[0].equals("D") + ? Task.TaskType.DEADLINE + : Task.TaskType.EVENT; + + boolean taskDone = lineSplit[1].equals("0") ? false : true; + + Task currentTask; + + switch (taskType) { + case TODO: + currentTask = new TodoTask(lineSplit[2]); + break; + + case DEADLINE: + currentTask = new DeadlineTask(lineSplit[2], lineSplit[3]); + break; + + case EVENT: + currentTask = new EventTask(lineSplit[2], lineSplit[3]); + break; + + default: + throw new IllegalArgumentException("Corrupted file"); + } + if (taskDone ) { + currentTask.makeDone(); + } + return currentTask; + + } + + +} diff --git a/src/main/java/duke/utils/TaskList.java b/src/main/java/duke/utils/TaskList.java new file mode 100644 index 0000000000..64bcb1478a --- /dev/null +++ b/src/main/java/duke/utils/TaskList.java @@ -0,0 +1,68 @@ +package duke.utils; + +import java.util.ArrayList; + +import duke.tasks.Task; + +/** + * Class that represents a list of tasks + */ +public class TaskList { + private ArrayList tasks; + + /** + * Constructor that initializes a TaskList object + * @param tasks + */ + public TaskList(ArrayList tasks) { + this.tasks = tasks; + } + + /** + * Adds a given task to the TaskList + * + * @param task + */ + public void addTask(Task task) { + tasks.add(task); + } + + /** + * Getting a task at a specified index in the TaskList + * + * @param index + * @return + */ + public Task getTask(int index) { + assert (index < tasks.size()); + return tasks.get(index); + } + + /** + * Removing a task from the specified index in the TaskList + * @param index + */ + public void removeTask(int index) { + assert (index < tasks.size()); + tasks.remove(index); + } + + // 4. clearing tasklist + + /** + * Clears the TaskList of all tasks + */ + public void clearTaskList() { + tasks.clear(); + } + + // 5. checking number of duke.tasks in tasklist + + /** + * @return int number of tasks in the TaskList + */ + public int numberOfTasks() { + return tasks.size(); + } + +} diff --git a/src/main/java/duke/utils/Ui.java b/src/main/java/duke/utils/Ui.java new file mode 100644 index 0000000000..c4b0053706 --- /dev/null +++ b/src/main/java/duke/utils/Ui.java @@ -0,0 +1,246 @@ +package duke.utils; + +import java.util.Scanner; + +import duke.commands.Command; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.stage.Stage; + +/** + * Class that handles taking in user input + */ +public class Ui { + + private static final String LOGO = + " ____ _ \n" + + "| _ \\ _ _| | _____ \n" + + "| | | | | | | |/ / _ \\\n" + + "| |_| | |_| | < __/\n" + + "|____/ \\__,_|_|\\_\\___|\n"; + + private static final String INTRO = "Hello from\n" + LOGO + "\n" + "What can I do for you?"; + private static final String ENDING = "Bye. Hope to see you again soon!\n"; + + private Image user = new Image(this.getClass().getResourceAsStream("/images/DaUser.png")); + private Image duke = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png")); + + + private Storage storage; + private TaskList tasklist; + private Parser parser; + + private Scanner scanner; + + private Stage stage; + private ScrollPane scrollPane; + private VBox vbox; + private TextField textField; + private Label label; + private StringBuilder stringBuilder; + private Button sendButton; + private AnchorPane mainLayout; + + + /** + * Constructor that initializes a Ui object + */ + public Ui(Stage stage, TaskList taskList, Storage storage, Parser parser) { + scanner = new Scanner(System.in); + this.stage = stage; + this.tasklist = taskList; + this.storage = storage; + this.parser = parser; + this.scrollPane = new ScrollPane(); + this.vbox = new VBox(); + this.textField = new TextField(); + this.label = new Label(); + this.stringBuilder = new StringBuilder(); + this.sendButton = new Button("Send"); + this.mainLayout = new AnchorPane(); + + + label.setWrapText(true); + vbox.setAlignment(Pos.TOP_LEFT); + vbox.getChildren().addAll(textField, label); + + scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS); + scrollPane.setContent(vbox); + } + + /** + * Initializes the UI by displaying the stage of the javafx application. + */ + public void init() { + setInitialStage(); + start(); + formatStage(); + setUserInteraction(); + } + + /** + * Sets the initial stage display. + */ + public void setInitialStage() { + stage.setTitle("Mr Duke's Todo App"); + Color bgColor = Color.AZURE; + + mainLayout.getChildren().addAll(scrollPane, textField, sendButton); + + Scene scene = new Scene(mainLayout, bgColor); + + stage.setScene(scene); // Setting the stage to show our screen + stage.show(); + } + + /** + * Formats the window to look as expected. + */ + public void formatStage() { + //@@author ramaven-reused + //Reused from JavaFx tutorial 2 (https://se-education.org/guides/tutorials/javaFxPart2.html) + // with minor modifications + { + stage.setResizable(false); + stage.setMinHeight(600.0); + stage.setMinWidth(400.0); + + mainLayout.setPrefSize(400.0, 600.0); + + scrollPane.setPrefSize(385, 535); + scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS); + + scrollPane.setVvalue(1.0); + scrollPane.setFitToWidth(true); + + vbox.setPrefHeight(Region.USE_COMPUTED_SIZE); + + textField.setPrefWidth(325.0); + + sendButton.setPrefWidth(55.1); + + AnchorPane.setTopAnchor(scrollPane, 1.0); + + AnchorPane.setBottomAnchor(sendButton, 1.0); + AnchorPane.setRightAnchor(sendButton, 1.0); + + AnchorPane.setLeftAnchor(textField, 1.0); + AnchorPane.setBottomAnchor(textField, 1.0); + } + //@@author + } + + /** + * Adds functionality to handle user input. + */ + public void setUserInteraction() { + //@@author ramaven-reused + //Reused from JavaFx tutorial 3 (https://se-education.org/guides/tutorials/javaFxPart3.html) + // with minor modifications + { + sendButton.setOnMouseClicked((event) -> { + handleUserInput(); + }); + + //Scroll down to the end every time dialogContainer's height changes to + // improve user experience + vbox.heightProperty().addListener((observable) -> scrollPane.setVvalue(1.0)); + } + //@@author + } + + + /** + * 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. + */ + private void handleUserInput() { + // Solution below adapted from JavaFx tutorial 3 (https://se-education.org/guides/tutorials/javaFxPart3.html) + { + Label userText = new Label(textField.getText()); + vbox.getChildren().addAll(DialogBox.getUserDialog(userText, new ImageView(user))); + Label dukeText = new Label(getResponse(textField.getText())); + vbox.getChildren().addAll(DialogBox.getDukeDialog(dukeText, new ImageView(duke))); + textField.clear(); + } + } + + /** + * Generates a response to user input. + */ + private String getResponse(String input) { + String userInput = textField.getText(); + stringBuilder.append(userInput); + label.setText(stringBuilder.toString()); + + try { + Command c = parser.parseInput(userInput); + c.execute(tasklist, Ui.this, storage); + } catch (Exception ex) { + showError(ex); + } finally { + showLine(); + } + + return ""; + } + + /** + * Prints the message in the UI + * @param message message to be shown on Duke + */ + public void printResponse(String message) { + vbox.getChildren().addAll( + DialogBox.getDukeDialog(new Label(message))); + + + } + + /** + * Returns the intro statement when app first launches + */ + public void start() { + vbox.getChildren().addAll( + DialogBox.getDukeDialog(new Label(INTRO), new ImageView(duke) )); + showLine(); + } + + /** + * returns the ending statement before app closes + */ + public void end() { + vbox.getChildren().addAll( + DialogBox.getDukeDialog(new Label(ENDING), new ImageView(duke) )); + showLine(); + stage.close(); + } + + + /** + * Prints a dashed divider line + */ + public void showLine() { + vbox.getChildren().addAll( + DialogBox.getDukeDialog(new Label("\n________________________________________________________ \n"))); + } + + /** + * Prints out the string message of the given Exception. + * @param e Exception + */ + public void showError(Exception e) { + vbox.getChildren().addAll( + DialogBox.getDukeDialog(new Label(e.toString()) )); + } +} diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png new file mode 100644 index 0000000000..edabfe6acb 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..9ee8fa85cd Binary files /dev/null and b/src/main/resources/images/DaUser.png differ diff --git a/src/test/java/duke/tasks/TodoTaskTest.java b/src/test/java/duke/tasks/TodoTaskTest.java new file mode 100644 index 0000000000..517b60d7cc --- /dev/null +++ b/src/test/java/duke/tasks/TodoTaskTest.java @@ -0,0 +1,19 @@ +package duke.tasks; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class TodoTaskTest { + @Test + public void todoTaskNameTest() { + TodoTask task = new TodoTask("Test task"); + assertEquals("Test task", task.getName()); + } + + @Test + public void todoTaskTypeTest() { + TodoTask task = new TodoTask("Test task"); + assertEquals(Task.TaskType.TODO, task.getType()); + } +} diff --git a/src/test/java/duke/utils/DukeDateTest.java b/src/test/java/duke/utils/DukeDateTest.java new file mode 100644 index 0000000000..a09838dd46 --- /dev/null +++ b/src/test/java/duke/utils/DukeDateTest.java @@ -0,0 +1,25 @@ +package duke.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class DukeDateTest { + + @Test + public void formatDateTimeTest() { + String literalDateTime = "2/12/2019 1800"; + String parsedDate = DukeDate.parseDateToString(DukeDate.formatDate(literalDateTime)); + String expected = "Mon, Dec 02 2019, 18:00"; + assertEquals(expected, parsedDate); + } + + @Test + public void formatDateTest() { + String literalDateTime = "2/12/2019"; + String parsedDate = DukeDate.parseDateToString(DukeDate.formatDate(literalDateTime)); + String expected = "Mon, Dec 02 2019, 00:00"; + assertEquals(expected, parsedDate); + } + +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e7..348b7cd3f6 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -5,3 +5,5 @@ Hello from | |_| | |_| | < __/ |____/ \__,_|_|\_\___| +Hello! I'm Duke +What can I do for you? diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh old mode 100644 new mode 100755