diff --git a/.gitignore b/.gitignore index f69985ef1f..6b2a403cf0 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ bin/ /text-ui-test/ACTUAL.txt text-ui-test/EXPECTED-UNIX.TXT +*.class +/data/duke.txt diff --git a/README.md b/README.md index 8715d4d915..5d916655ae 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,31 @@ -# Duke project template +# Duke v0.9.0 +> *"Be not afraid of growing slowly, be afraid of standing still."* - Chinese proverb -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. +Duke offers you a: +- Hassle free +- Text based +- Minimalist +and completely **offline** experience of planning your tasks! -## Setting up in Intellij +## Duke's features +- [x] Adding and saving tasks +- [ ] GUI (coming soon!) +- [ ] Mystery features 😜 -Prerequisites: JDK 11, update Intellij to the most recent version. +## Setting up Duke +1. Download it [here](https://github.com/WangGLJoseph/ip/releases/download/A-Jar/ip.jar) +2. Double-click to run it +3. Add your tasks +4. Enjoy **mastery** over your tasks 😆 -1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project first) -1. Open the project into Intellij as follows: - 1. Click `Open`. - 1. Select the project directory, and click `OK`. - 1. If there are any further prompts, accept the defaults. -1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
- In the same dialog, set the **Project language level** field to the `SDK default` option. -3. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: - ``` - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| +A snippet of the workings of controlling your tasks. Behold, you've got the power! + ```java + public String finishTask(int index) throws DukeException { + if (index > taskArrayList.size()) { + throw new DukeException("This task index is not in the task list!"); + } + taskArrayList.get(index - 1).markAsDone(); + return (sandwich("Congratulations! You have finished this task: " + + taskArrayList.get(index - 1).toString())); + } ``` diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..773345bf9c --- /dev/null +++ b/build.gradle @@ -0,0 +1,60 @@ +plugins { + id 'java' + id 'application' + id 'checkstyle' + id 'com.github.johnrengelman.shadow' version '5.1.0' +} + +repositories { + mavenCentral() +} + +dependencies { + String javaFxVersion = '11' + + 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 = "cs2103.duke.Duke" +} + +shadowJar { + archiveBaseName = "duke" + archiveClassifier = null +} + +checkstyle { + toolVersion = '8.29' +} + +run { + standardInput = System.in +} diff --git a/docs/README.md b/docs/README.md index 8077118ebe..327812f9cc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,24 +1,93 @@ -# User Guide +# User Guide for Duke ## Features -### Feature-ABC +### Adding tasks -Description of the feature. +Duke allows you to add 3 types of tasks: +- Todo +- Deadline +- Event -### Feature-XYZ +### Saving tasks -Description of the feature. +Duke saves your tasks automatically when you exit the program. -## Usage +### Deleting tasks + +Duke lets you delete any task from your task list. + +### Finishing tasks + +Duke lets you mark any task on your task list as complete, without deleting them. + +### Undoing previous command + +Duke lets you undo your last undo-able command, say goodbye to accidentally deleting a task! -### `Keyword` - Describe action +### List tasks on your task list -Describe the action and its outcome. +Duke lets you see everything on your task list at a glance. + + +## Usage + +### `todo` - Adds a todo task Example of usage: -`keyword (optional arguments)` +`todo nap` + +Expected outcome: + +The todo task 'nap' will be added to your task list. + +``` +New todo task added: +[T][ ] nap +You now have x item(s) in your task list. +``` + + +### `deadline` - Adds a deadline task + +Example of usage: + +`ip due /by 2021-09-20` + +Expected outcome: + +The deadline task 'ip due' will be added to your task list. + +``` +New deadline task added: +[D][ ] ip due (by: 2021 09 20) +You now have x item(s) in your task list. +``` + + +### `event` - Adds an event task + +Example of usage: + +`event family meal /at 2021-09-21` + +Expected outcome: + +The event task 'family meal' will be added to your task list. + +``` +New event task added: +[E][ ] family meal (by: 2021 09 21) +You now have x item(s) in your task list. +``` + + +### `bye` - Saves tasks and exits the program + +Example of usage: + +`bye` Expected outcome: @@ -27,3 +96,80 @@ Description of the outcome. ``` expected output ``` + + +### `delete` - Deletes a task from your task list + +Deletes the task at the indicated index. + +Example of usage: + +`delete 3` + +Expected outcome: + +Description of the outcome. + +``` +1.[T][ ] nap +2.[D][ ] ip submit (by: 2021 09 20) +``` + + +### `done` - Marks a task as done + +Marks the task at the indicated index as done with an 'X'. + +Example of usage: + +`done 1` + +Expected outcome: + +The task at the indicated index will be marked as done. + +``` +Congratulations! You have finished this task: +[T][X] nap +``` + + +### `undo` - Undoes the last command + +Undoes the last valid undo-able command. + +Example of usage: + +`undo` + +Expected outcome: + +The previous command's effect will be reverted. + +``` +I undid your previous command: +done 1 +Here is your current task list: +1.[T][ ] nap +2.[D][ ] ip due (by: 2021 09 20) +3.[E][ ] family meal (at: 2021 09 21) +``` + + +### `list` - Lists all tasks in the task list. + +Returns all the tasks in the task list + +Example of usage: + +`list` + +Expected outcome: + +A list of all the tasks on your task list. + +``` +1.[T][ ] nap +2.[D][ ] ip due (by: 2021 09 20) +3.[E][ ] family meal (at: 2021 09 21) +``` \ No newline at end of file diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..0538bf8b40 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..ddeb671b60 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-time-machine \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..f3d88b1c2f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..b7c8c5dbf5 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..2fe81a7d95 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..62bd9b9cce --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,103 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334cc..0000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..6fd745921d --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: cs2103.duke.Duke + diff --git a/src/main/java/cs2103/duke/Deadline.java b/src/main/java/cs2103/duke/Deadline.java new file mode 100644 index 0000000000..bc3b971547 --- /dev/null +++ b/src/main/java/cs2103/duke/Deadline.java @@ -0,0 +1,25 @@ +package cs2103.duke; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * This class encapsulates a Deadline Task object, which inherits from the Task class. + */ +public class Deadline extends Task { + protected int index; + protected String by; + protected LocalDate date; + + public Deadline(int index, String description, String by) { + super(index, description); + this.index = index; + this.by = by; + this.date = LocalDate.parse(by); + } + + @Override + public String toString() { + return "[D]" + super.toString() + " (by: " + date.format(DateTimeFormatter.ofPattern("yyyy MM dd")) + ")"; + } +} \ No newline at end of file diff --git a/src/main/java/cs2103/duke/Duke.java b/src/main/java/cs2103/duke/Duke.java new file mode 100644 index 0000000000..c19606ce6f --- /dev/null +++ b/src/main/java/cs2103/duke/Duke.java @@ -0,0 +1,44 @@ +package cs2103.duke; + +import java.io.IOException; + +/** + * This class encapsulates a Duke chat-bot. + */ +public class Duke { + private TaskList tasks; + private Ui ui; + + public Duke() { + Storage storage = new Storage(); + try { + tasks = new TaskList(storage.load()); + } catch (DukeException | IOException e) { + System.out.println(ui.showLoadingError()); + tasks = new TaskList(); + } + ui = new Ui(storage, tasks); + } + + /** + * Generates a response given an input from user. + * + * @param input The user input received. + * @return A string representing the response to the user. + */ + public String getResponse(String input) { + assert input != null; + String result; + try { + return ui.handleInput(input, tasks); + } catch (DukeException | IOException e) { + e.printStackTrace(); + result = e.toString(); + } + return result; + } + + public static void main(String[] args) { + DukeLauncher.main(null); + } +} \ No newline at end of file diff --git a/src/main/java/cs2103/duke/DukeException.java b/src/main/java/cs2103/duke/DukeException.java new file mode 100644 index 0000000000..32442b5633 --- /dev/null +++ b/src/main/java/cs2103/duke/DukeException.java @@ -0,0 +1,18 @@ +package cs2103.duke; + +/** + * This class encapsulates the exception thrown when the user enters an erroneous input. + */ +public class DukeException extends Exception { + private static String msg; + + public DukeException(String errorMessage) { + super(errorMessage); + msg = errorMessage; + } + + @Override + public String toString() { + return ("Oh No! I do not understand your input due to the following problem:\n" + msg); + } +} diff --git a/src/main/java/cs2103/duke/DukeLauncher.java b/src/main/java/cs2103/duke/DukeLauncher.java new file mode 100644 index 0000000000..a4b1e90965 --- /dev/null +++ b/src/main/java/cs2103/duke/DukeLauncher.java @@ -0,0 +1,12 @@ +package cs2103.duke; + +import javafx.application.Application; + +/** + * A launcher class to workaround classpath issues. + */ +public class DukeLauncher { + public static void main(String[] args) { + Application.launch(Main.class, args); + } +} diff --git a/src/main/java/cs2103/duke/DukeParser.java b/src/main/java/cs2103/duke/DukeParser.java new file mode 100644 index 0000000000..2512fb5877 --- /dev/null +++ b/src/main/java/cs2103/duke/DukeParser.java @@ -0,0 +1,126 @@ +package cs2103.duke; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Scanner; + +/** + * This class encapsulates the parsing of duke.txt to convert the contents of the file + * into an ArrayList. + */ +public class DukeParser { + private String filePath; + + public DukeParser(String dukeFilePath) { + this.filePath = dukeFilePath; + } + + /** + * This method copies the contents of the file from filePath and + * converts them into Task objects into the taskArrayList with the + * help of the parse() method. + * + * @return TaskArrayList the ArrayList to copy the information to. + * @throws IOException If filePath does not exist. + */ + public ArrayList copyFileContents() throws IOException { + assert filePath != null; + File f = new File(this.filePath); // create a File for the given file path + Scanner s = new Scanner(f); // create a Scanner using the File as the source + StringBuilder targetBuilder = new StringBuilder(); + while (s.hasNext()) { + targetBuilder.append(s.nextLine()).append("\n"); + } + + return parse(targetBuilder.toString()); + } + + /** + * This method parses the string copied from duke.txt and converts them into task objects + * into the taskArrayList. + * + * @param toParse The string to parse. + * @return A taskArrayList made up of tasks. + */ + public static ArrayList parse(String toParse) { + ArrayList result = new ArrayList<>(); + Scanner ps = new Scanner(toParse); // passes whole file into the scanner + while (ps.hasNextLine()) { + String nLine = ps.nextLine(); // parse one line at a time + int ref = 3; // reference point + char taskType = nLine.charAt(ref); + switch (taskType) { + case 'T': + parseTodo(ref, nLine, result); + break; + case 'D': + parseDeadline(ref, nLine, result); + break; + case 'E': + parseEvent(ref, nLine, result); + break; + default: + System.out.println("Unknown input"); + break; + } + } + return result; + } + + public static void parseTodo(int ref, String input, ArrayList result) { + int strLength = input.length(); + boolean isDone = input.charAt(ref + 3) == 'X'; + String todoName = input.substring(ref + 5, strLength).trim(); + Task newestTodo = new ToDo(result.size(), todoName); + if (isDone) { + newestTodo.markAsDone(); + } + result.add(newestTodo); + } + + public static void parseDeadline(int ref, String input, ArrayList result) { + int strLength = input.length(); + boolean isDone = input.charAt(ref + 3) == 'X'; + String deadlineInfo = input.substring(ref + 5, strLength); + String[] arrD = deadlineInfo.split("\\(by: ", 2); + String deadlineName = arrD[0].trim(); + String deadlineReminder = arrD[1].substring(0, arrD[1].length() - 1).trim(); + deadlineReminder = parseDate(deadlineReminder); + Task newestDeadline = new Deadline(result.size(), deadlineName, deadlineReminder); + result.add(newestDeadline); + if (isDone) { + newestDeadline.markAsDone(); + } + } + + public static void parseEvent(int ref, String input, ArrayList result) { + int strLength = input.length(); + boolean isDone = input.charAt(ref + 3) == 'X'; + String eventInfo = input.substring(ref + 5, strLength); + String[] arrE = eventInfo.split("\\(at: ", 2); + String eventName = arrE[0].trim(); + String eventReminder = arrE[1].substring(0, arrE[1].length() - 1).trim(); + eventReminder = parseDate(eventReminder); + Task newestEvent = new Event(result.size(), eventName, eventReminder); + result.add(newestEvent); + if (isDone) { + newestEvent.markAsDone(); + } + } + + /** + * Parses input string in the format "yyyy MM dd" and returns it in the format + * "YYYY-MM-DD". + * + * @param input String in format "MMM d yyyy". + * @return String in format "YYYY-MM-DD". + */ + public static String parseDate(String input) { + String year = input.substring(0, 4); + String month = input.substring(5, 7); + String day = input.substring(8, 10); + return year + "-" + month + "-" + day; + } + +} diff --git a/src/main/java/cs2103/duke/Event.java b/src/main/java/cs2103/duke/Event.java new file mode 100644 index 0000000000..969481b471 --- /dev/null +++ b/src/main/java/cs2103/duke/Event.java @@ -0,0 +1,25 @@ +package cs2103.duke; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * This class encapsulates an Event Task object, which inherits from the Task class. + */ +public class Event extends Task { + protected int index; + protected String at; + protected LocalDate date; + + public Event(int index, String description, String at) { + super(index, description); + this.index = index; + this.at = at; + this.date = LocalDate.parse(at); + } + + @Override + public String toString() { + return "[E]" + super.toString() + " (at: " + date.format(DateTimeFormatter.ofPattern("yyyy MM dd")) + ")"; + } +} \ No newline at end of file diff --git a/src/main/java/cs2103/duke/Main.java b/src/main/java/cs2103/duke/Main.java new file mode 100644 index 0000000000..17c5b61399 --- /dev/null +++ b/src/main/java/cs2103/duke/Main.java @@ -0,0 +1,38 @@ +package cs2103.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; + +import cs2103.duke.controllers.MainWindow; + +/** + * A GUI for Duke using FXML. + */ +public class Main extends Application { + + private final Duke duke = new Duke(); + + public Main() { + } + + @Override + public void start(Stage stage) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml")); + stage.setTitle("Duke"); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + scene.getRoot().setStyle("-fx-font-family: 'Courier New'"); + 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/cs2103/duke/Storage.java b/src/main/java/cs2103/duke/Storage.java new file mode 100644 index 0000000000..e01175b8d6 --- /dev/null +++ b/src/main/java/cs2103/duke/Storage.java @@ -0,0 +1,67 @@ +package cs2103.duke; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; + +/** + * This class encapsulates a Storage object which saves the tasks that have been added by the user, it also + * performs the initialization for the duke.txt and its parent folder, if they are not already present. + */ +public class Storage { + String dukeFileDirectoryPath = "./data"; + String dukeFilePath = "./data/duke.txt"; + + public Storage() { + initialize(); + } + + /** + * Checks the dukeFilePath for the existence of duke.txt, if the file or the folder containing it + * does not exist, create them. + */ + public void initialize() { + File dukeFileDirectory = new File(dukeFileDirectoryPath); + File dukeFile = new File(dukeFilePath); + // creates directory if it does not exist + if (dukeFileDirectory.mkdir()) { + System.out.println("folder: 'data/' has been created"); + } + // creates file if it does not exist + try { + if (dukeFile.createNewFile()) { + System.out.println("'duke.txt' has been created in the 'data/' folder "); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Loads the data stored in duke.txt into a taskArrayList. + * + * @return The taskArrayList representation of the data stored in duke.txt, if any. + * @throws IOException If an invalid input is detected. + */ + public ArrayList load() throws IOException { + // initialize + initialize(); + // load the data from the hard disk for dukeFile + DukeParser p = new DukeParser(dukeFilePath); + return p.copyFileContents(); + } + + /** + * This method overwrites the file. + * + * @param textToAppend The text to append. + * @throws IOException If filePath does not exist. + */ + + public void overwriteFile(String textToAppend) throws IOException { + FileWriter fw = new FileWriter(dukeFilePath, false); // create a FileWriter in append mode + fw.write(textToAppend); + fw.close(); + } +} diff --git a/src/main/java/cs2103/duke/Task.java b/src/main/java/cs2103/duke/Task.java new file mode 100644 index 0000000000..ed94439ebe --- /dev/null +++ b/src/main/java/cs2103/duke/Task.java @@ -0,0 +1,64 @@ +package cs2103.duke; + +/** + * This class encapsulates a Task object. + */ + +public class Task { + protected int index; + protected String description; + protected boolean isDone; + + /** + * Creates a task with the specified description, isDone false by default. + * + * @param index Index of the task, starting from 0. + * @param description Description of the task. + + */ + public Task(int index, String description) { + this.index = index; + this.description = description; + this.isDone = false; + } + + public int getIndex() { + return index; + } + + public String getDescription() { + return description; + } + + /** + * Getter method for status using isDone. + * + * @return A letter 'X' if task is complete, an empty space if otherwise. + */ + public String getStatusIcon() { + return (isDone ? "X" : " "); // mark done task with X + } + + /** + * Sets a Task object as done. + */ + public void markAsDone() { + this.isDone = true; + } + + /** + * Sets a Task object as not done. + */ + public void markAsNotDone() { + this.isDone = false; + } + + /** + * Returns string representation of Task. + * + * @return String representation of Task. + */ + public String toString() { + return ("[" + getStatusIcon() + "] " + description); + } +} diff --git a/src/main/java/cs2103/duke/TaskList.java b/src/main/java/cs2103/duke/TaskList.java new file mode 100644 index 0000000000..c9b2c3b5dd --- /dev/null +++ b/src/main/java/cs2103/duke/TaskList.java @@ -0,0 +1,350 @@ +package cs2103.duke; + +import java.util.ArrayList; + +/** + * This class encapsulates a TaskList object, which represents all the tasks added by a user. + */ +public class TaskList { + private ArrayList taskArrayList; + protected String previousInput; + protected ArrayList previousTaskList; + + /** + * This constructor instantiates a new Tasklist with no tasks inside. + */ + public TaskList() { + taskArrayList = new ArrayList<>(); + } + + /** + * This constructor instantiates a new TaskList with tasks provided by the input taskArrayList. + * + * @param taskArrayList The ArrayList containing the tasks with which to instantiate the new TaskList object. + * @throws DukeException If an invalid input is detected. + */ + public TaskList(ArrayList taskArrayList) throws DukeException { + this.taskArrayList = taskArrayList; + } + + /** + * This method takes the user's input list and beautifies it for display. + * + * @return The beautified string to display. + */ + public String listBeautify() { + StringBuilder listBeautified = new StringBuilder(); + for (int i = 0; i < taskArrayList.size(); i++) { + listBeautified.append(taskArrayList.get(i).getIndex() + 1) + .append(".") + .append(taskArrayList.get(i).toString()); + if (i < taskArrayList.size() - 1) { // new line except for last item + listBeautified.append("\n"); + } + } + return listBeautified.toString(); + } + + /** + * Validates input string to ensure it follows the valid format YYYY-MM-DD, and is a valid date. + * + * @param input The string to be validated. + * @return True if the string is a valid date. + */ + public boolean isValidDate(String input) { + String[] splitInputs = input.split("-"); + if (splitInputs.length != 3) { + return false; + } + boolean isLeapYear; + String year = splitInputs[0]; + String month = splitInputs[1]; + String day = splitInputs[2]; + int maxDay; + + // check year + if (year.length() != 4 || !year.matches("\\d+")) { + return false; + } else { + isLeapYear = (Integer.parseInt(year) % 4 == 0); + } + + // check month + if (month.length() != 2 || !month.matches("\\d+")) { + return false; + } else { + switch (Integer.parseInt(month)) { + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12: + maxDay = 31; + break; + case 4: + case 6: + case 9: + case 11: + maxDay = 30; + break; + case 2: + if (isLeapYear) { + maxDay = 29; + } else { + maxDay = 28; + } + break; + default: + return false; + } + } + + // check day + if (day.length() != 2 || !day.matches("\\d+")) { + return false; + } else { + return Integer.parseInt(day) <= maxDay; + } + } + + /** + * Adds a todo task into the task list. + * + * @param input The input containing the remaining user input after the command. + * @return String representing the newly added todo task. + * @throws DukeException if the user input is invalid. + */ + public String addTodo(String input) throws DukeException { + if (input.trim().equals("")) { + throw new DukeException("No task name"); + } + Task newestTodo = new ToDo(taskArrayList.size(), input); + // using copy by value, store current taskArrayList in a buffer, in case of an undo command later. + previousTaskList = new ArrayList<>(taskArrayList); + taskArrayList.add(newestTodo); + return ("New todo task added:\n" + + newestTodo + + "\nYou now have " + + taskArrayList.size() + + " item(s) in your task list."); + } + + /** + * Adds a deadline task into the task list. + * + * @param input The input containing the remaining user input after the command. + * @return String representing the newly added deadline task. + * @throws DukeException if the user input is invalid. + */ + public String addDeadline(String input) throws DukeException { + String[] deadlineTokens = input.split("\\s*/by\\s*"); + if (deadlineTokens.length == 0) { + throw new DukeException("No task description"); + } else if (deadlineTokens.length == 1) { + throw new DukeException("No task deadline"); + } + String name = deadlineTokens[0]; + String description = deadlineTokens[1]; + if (!isValidDate(description)) { + throw new DukeException("Invalid Deadline Date, please follow the format YYYY-MM-DD"); + } + Task newestDeadline = new Deadline(taskArrayList.size(), name, description); + // using copy by value, store current taskArrayList in a buffer, in case of an undo command later. + previousTaskList = new ArrayList<>(taskArrayList); + taskArrayList.add(newestDeadline); + return ("New deadline task added:\n" + + newestDeadline + + "\nYou now have " + + taskArrayList.size() + + " item(s) in your task list."); + } + + /** + * Adds an event task into the task list. + * + * @param input The input containing the remaining user input after the command. + * @return String representing the newly added event task. + * @throws DukeException if the user input is invalid. + */ + public String addEvent(String input) throws DukeException { + String[] eventTokens = input.split("\\s*/at\\s*"); + if (eventTokens.length == 0) { + throw new DukeException("No task description"); + } else if (eventTokens.length == 1) { + throw new DukeException("No task duration"); + } + String name = eventTokens[0]; + String description = eventTokens[1]; + if (!isValidDate(description)) { + throw new DukeException("Invalid Event Date, please follow the format YYYY-MM-DD"); + } + Task newestEvent = new Event(taskArrayList.size(), name, description); + // using copy by value, store current taskArrayList in a buffer, in case of an undo command later. + previousTaskList = new ArrayList<>(taskArrayList); + taskArrayList.add(newestEvent); + return ("New deadline task added:\n" + + newestEvent + + "\nYou now have " + + taskArrayList.size() + + " item(s) in your task list."); + } + + /** + * This method lists all tasks in the TaskList. + * + * @return A string representing all tasks in the TaskList. + */ + public String listTasks() { + return (listBeautify()); + } + + /** + * This method marks a task as finished, and returns a string informing the user of the change. + * + * @param index The index of the task to be marked as finished, the first task will have an index of 1. + * @return A string informing the user of the change. + * @throws DukeException If the user specified an invalid index. + */ + public String finishTask(int index) throws DukeException { + assert index > 0; + if (index > taskArrayList.size()) { + throw new DukeException("This task index is not in the task list!"); + } + taskArrayList.get(index - 1).markAsDone(); + return ("Congratulations! You have finished this task:\n" + + taskArrayList.get(index - 1).toString()); + } + + /** + * This method marks a task as unfinished, and returns a string informing the user of the change. + * + * @param index The index of the task to be marked as unfinished, the first task will have an index of 1. + * @return A string informing the user of the change. + * @throws DukeException If the user specified an invalid index. + */ + public String unFinishTask(int index) throws DukeException { + assert index > 0; + if (index > taskArrayList.size()) { + throw new DukeException("This task index is not in the task list!"); + } + taskArrayList.get(index - 1).markAsNotDone(); + return ("Got it, marking this task as unfinished:\n" + + taskArrayList.get(index - 1).toString()); + } + + /** + * This method removes a task from the TaskList, and lets the user know which task was removed. + * + * @param index The index of the task to be deleted, the first task will have an index of 1. + * @return A string informing the user of the deletion. + * @throws DukeException If the user specified an invalid index. + */ + public String deleteTask(int index) throws DukeException { + assert index > 0; + if (index > taskArrayList.size()) { + throw new DukeException("This task index is not in the task list!"); + } + // using copy by value, store current taskArrayList in a buffer, in case of an undo command later. + previousTaskList = new ArrayList<>(taskArrayList); + String deleteMessage = ("Got it, I have deleted this task:\n" + + taskArrayList.get(index - 1).toString() + + "\nYou now have " + + (taskArrayList.size() - 1) + + " item(s) in your task list."); + taskArrayList.remove(index - 1); + return deleteMessage; + } + + /** + * This method finds all tasks matching a keyword entered by the user. + * + * @param keyword The keyword to match tasks to. + * @return All tasks matching the keyword. + * @throws DukeException If keyword is invalid. + */ + public String findTasks(String keyword) throws DukeException { + assert keyword != null; + // if 'keyword' contains more than one word or any spaces + if (keyword.trim().split("\\s+").length > 1) { + throw new DukeException("Invalid keyword, please enter only one keyword without blanks"); + } + ArrayList subList = new ArrayList<>(); + for (Task task : taskArrayList) { + if (task.description.contains(keyword)) { + subList.add(task); + } + } + if (subList.isEmpty()) { + return ("No matches found for keyword: " + keyword.trim()); + } + TaskList matchedTaskList = new TaskList(subList); + return ("Here are the matching tasks in your list:\n" + + matchedTaskList.listBeautify() + ); + } + + /** + * This method undoes the user's previous command, if it can be undone. + * + * @return A string representing if the previous command was deleted. + * @throws DukeException If previous command cannot be undone. + */ + public String undo() throws DukeException { + if (previousInput == null) { + throw new DukeException("Nothing to Undo"); + } + // parse out the first word from input as the user's command + String[] userPreviousInputs = previousInput.split("\\s+", 2); + String previousCommand = userPreviousInputs[0]; + String remainingPreviousInput = ""; + if (userPreviousInputs.length > 1) { + remainingPreviousInput = userPreviousInputs[1]; + } + + switch (previousCommand) { + case "todo": + case "deadline": + case "event": + case "delete": + taskArrayList = new ArrayList<>(previousTaskList); + String undoMessage = "I undid your previous command: \n" + + previousInput + + "\nhere is your current task list:\n" + + listBeautify(); + previousTaskList = new ArrayList<>(); + previousInput = ""; + return undoMessage; + case "done": + // finishTask() manipulates the Task objects directly, a shallow copy of task ArrayList is not enough + undoDoneCommand(remainingPreviousInput); + String undoMessageDone = "I undid your previous command: \n" + + previousInput + + "\nhere is your current task list:\n" + + listBeautify(); + previousTaskList = new ArrayList<>(); + previousInput = ""; + return undoMessageDone; + default: + throw new DukeException("Cannot undo this command"); + } + } + + /** + * This method is a helper method to handle the undo-ing of marking a task as done. + * + * @param input The remaining parts of the user's previous command without the command word. + */ + public void undoDoneCommand(String input) throws DukeException { + if (input == null) { + throw new DukeException("unspecified task to mark as done"); + } + try { + int taskNumber = Integer.parseInt(input); + unFinishTask(taskNumber); + } catch (NumberFormatException ex) { + ex.printStackTrace(); + } + } +} diff --git a/src/main/java/cs2103/duke/ToDo.java b/src/main/java/cs2103/duke/ToDo.java new file mode 100644 index 0000000000..bdb1c59bbd --- /dev/null +++ b/src/main/java/cs2103/duke/ToDo.java @@ -0,0 +1,20 @@ +package cs2103.duke; + +/** + * This class encapsulates a Todo Task object, which inherits from the Task class. + */ +public class ToDo extends Task { + protected int index; + protected String description; + + public ToDo(int index, String description) { + super(index, description); + this.index = index; + this.description = description; + } + + @Override + public String toString() { + return "[T]" + super.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/cs2103/duke/Ui.java b/src/main/java/cs2103/duke/Ui.java new file mode 100644 index 0000000000..2254f5f7b8 --- /dev/null +++ b/src/main/java/cs2103/duke/Ui.java @@ -0,0 +1,119 @@ +package cs2103.duke; + +import java.io.IOException; + +/** + * This class encapsulates an Ui object, which abstracts the user interactions with Duke out of + * the Duke class. + */ +public class Ui { + private static final int lv = 10; + private static final String[] features = { + "", + "Greet, Echo, Exit, ", + "Add, List, \n", + "Mark as Done, ", + "ToDos, Events, Deadlines, ", + "Handle Errors, \n", + "Delete, ", + "Save, ", + "Dates and Times, ", + "Find\n" + }; + private final Storage storage; + private static TaskList taskArrayList; + + public Ui(Storage storage, TaskList tasks) { + this.storage = storage; + taskArrayList = tasks; + } + + /** + * Shows the goodbye message when the user exits Duke. + * + * @return The goodbye message to the user. + */ + public String showGoodbye() { + // Goodbye message + String goodbye = "Thank you for using Duke,\n" + + "See you soon!"; + return goodbye; + } + + /** + * Reads user's inputs and responds to it accordingly. + * + * @param input The information entered by the user after the command. + * @param tasks The Tasklist object containing all currently present tasks. + * @return The string representing the task added by the user. + * @throws DukeException If user enters an invalid input. + */ + public String handleInput(String input, TaskList tasks) throws DukeException, IOException { + // parse out the first word from input as the user's command + String[] userInputs = input.split("\\s+", 2); + String command = userInputs[0]; + String remainingInput = ""; + if (userInputs.length > 1) { + remainingInput = userInputs[1]; + } + + if (command.equals("undo")) { + if (userInputs.length > 1) { + throw new DukeException("Undo must be called on its own"); + } + return tasks.undo(); + } + + tasks.previousInput = input; + switch (command) { + case "bye": + storage.overwriteFile(taskArrayList.listBeautify()); + return showGoodbye(); + case "list": // user inputs 'list', return all text stored + return tasks.listTasks(); + case "done": // first input is done, check second input for integer + if (remainingInput == null) { + throw new DukeException("unspecified task to mark as done"); + } + try { + int taskNumber = Integer.parseInt(remainingInput); + return tasks.finishTask(taskNumber); + } catch (NumberFormatException ex) { + ex.printStackTrace(); + } + case "delete": + if (remainingInput == null) { + throw new DukeException("unspecified task to delete"); + } + try { + int taskNumber = Integer.parseInt(remainingInput); + return tasks.deleteTask(taskNumber); + } catch (NumberFormatException ex) { + ex.printStackTrace(); + } + case "todo": + return tasks.addTodo(remainingInput); + case "deadline": + return tasks.addDeadline(remainingInput); + case "event": + return tasks.addEvent(remainingInput); + case "find": + if (remainingInput == null) { + throw new DukeException("unspecified keyword to search for"); + } + return tasks.findTasks(remainingInput); + default: + throw new DukeException("Unknown Input"); // unknown input + } + } + + /** + * Alerts the user if the duke.txt could not be loaded. + * + * @return A string alerting the user of the error. + */ + public String showLoadingError() { + return ("Loading error: duke.txt could not be loaded"); + } + +} diff --git a/src/main/java/cs2103/duke/controllers/DialogBox.java b/src/main/java/cs2103/duke/controllers/DialogBox.java new file mode 100644 index 0000000000..5d34482623 --- /dev/null +++ b/src/main/java/cs2103/duke/controllers/DialogBox.java @@ -0,0 +1,60 @@ +package cs2103.duke.controllers; + +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; + +/** + * 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/cs2103/duke/controllers/MainWindow.java b/src/main/java/cs2103/duke/controllers/MainWindow.java new file mode 100644 index 0000000000..6491394ced --- /dev/null +++ b/src/main/java/cs2103/duke/controllers/MainWindow.java @@ -0,0 +1,83 @@ +package cs2103.duke.controllers; + +import javafx.animation.PauseTransition; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import cs2103.duke.Duke; +import javafx.util.Duration; + +/** + * Controller for cs2103.duke.controllers.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 static final String WELCOME_MESSAGE = "Welcome to Duke! What would you like to do today?"; + + private Image userImage = new Image(this.getClass().getResourceAsStream("/images/TheUser.png")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/TheDuke.png")); + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + dialogContainer.getChildren().addAll( + DialogBox.getDukeDialog(WELCOME_MESSAGE, dukeImage) + ); + } + + public void setDuke(Duke d) { + duke = d; + } + + /** + * 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. Duke's image is placed on the left to + * differentiate between user input and Duke’s output. + */ + @FXML + private void handleUserInput() { + String input = userInput.getText(); + String response = duke.getResponse(input); + assert input != null; + if (input.equals("bye")) { + addDialog(input, response); + // solution for delay before closing the window adapted from: + // https://github.com/felissaf/ip/blob/master/src/main/java/myjournal/MainWindow.java. + PauseTransition goodbyePause = new PauseTransition(Duration.seconds(2)); + goodbyePause.setOnFinished(event -> Platform.exit()); + goodbyePause.play(); + } else { + addDialog(input, response); + userInput.clear(); + } + } + + /** + * A helper method to add dialog for the user and duke. + * + * @param input The user's input. + * @param response The duke's response. + */ + private void addDialog(String input, String response) { + assert userImage != null; + assert dukeImage != null; + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(input, userImage), + DialogBox.getDukeDialog(response, dukeImage)); + } +} \ No newline at end of file diff --git a/src/main/resources/images/TheDuke.png b/src/main/resources/images/TheDuke.png new file mode 100644 index 0000000000..c61e87fb6f Binary files /dev/null and b/src/main/resources/images/TheDuke.png differ diff --git a/src/main/resources/images/TheUser.png b/src/main/resources/images/TheUser.png new file mode 100644 index 0000000000..d8a080f2b4 Binary files /dev/null and b/src/main/resources/images/TheUser.png differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..f5748243cc --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..bd369e2a80 --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + +