diff --git a/README.md b/README.md deleted file mode 100644 index 0dd8f7fb21..0000000000 --- a/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Duke project template - -This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. - -## Setting up in Intellij - -Prerequisites: JDK 11, update Intellij to the most recent version. - -1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project 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). -1. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below: - ``` - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - ``` diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..d734422c02 --- /dev/null +++ b/build.gradle @@ -0,0 +1,46 @@ + +plugins { + id 'java' + id 'application' + id 'checkstyle' + id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'org.openjfx.javafxplugin' version '0.0.9' +} + +application { + mainClassName = "Launcher" +} + +shadowJar { + archiveBaseName = "duke" + archiveClassifier = null +} + +repositories { + mavenCentral() +} + +test { + useJUnitPlatform() +} + +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' + +} \ No newline at end of file diff --git a/data/Duke.txt b/data/Duke.txt new file mode 100644 index 0000000000..e011863db6 --- /dev/null +++ b/data/Duke.txt @@ -0,0 +1,4 @@ +T||borrow book +E||meet friend | wed 2pm +D|X|return book |2020-08-25 +T||hello world diff --git a/docs/README.md b/docs/README.md index fd44069597..d9d1aae427 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,20 +1,75 @@ # User Guide - +JDK 11 ## Features -### Feature 1 -Description of feature. +### Command based Response +Bot that takes in a list of tasks to do and stores it in a list. +Tasks that are +done are marked as done with a cross. +#### Commands +list - lists all existing tasks -## Usage +todo, deadline, event {some task} - creates the respective task, date in yyyy-MM-dd -### `Keyword` - Describe action +delete {index} - deletes the respective task -Describe action and its outcome. +find {keyword} {search type (optional)} - retrieves matching task according to keyword +and optional search parameter (p [default, partial search case sensitive], pi [partial case insensitive], f [full exact search]) + +done {index} - marks task as done + +## Usage + +### `Keyword` `task to be added` `date if present` Example of usage: -`keyword (optional arguments)` +### Adding a todo task Read book + +`todo Read book` Expected outcome: -`outcome` +Retrieving the list by typing the string list as input will give: +Here are the tasks in your list: + +### Marking the todo task as done + +1.[T][] Read book + +`done 1` + +Expected Outcome: + +Nice! I've marked this task as done: + +1.[T][X] Read book + +### Deleting the todo task + +`delete 1` + +Noted. I've removed this task: + 1.[T][X] Read book +Now you have 0 tasks in your list. + +### Creating a deadline + +`deadline return book /by 2019-06-20` + +Got it. I've added this task: [D][] return book /by 2019-06-20 +Now you have 1 tasks in the list + +### Listing the tasks + +`list` + +Here are the tasks in your list: +1. [D][] return book (by: Jun 20 2019) + +### Finding a task (case insensitive search) + +`find BOOK pi` + +Here are the matching tasks in your list: +1. [D][] return book (by: Jun 20 2019) diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..0c9d986f5b 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..c741881743 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-slate \ 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..e708b1c023 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..2a563242c1 --- /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.8.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000000..4f906e0c81 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/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..107acd32c4 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@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 execute + +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 execute + +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 + +: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 %* + +: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/Deadline.java b/src/main/java/Deadline.java new file mode 100644 index 0000000000..8ef97ca935 --- /dev/null +++ b/src/main/java/Deadline.java @@ -0,0 +1,74 @@ +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.LocalDate; + +public class Deadline extends Task { + + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern( + "MMM d yyyy"); + + private LocalDate date; + + Deadline(String description) { + super(description); + } + + Deadline(String description, String eventDate) { + super(description, eventDate); + formatDate(); + } + +// public static void main(String[] args) { +// LocalDate date = LocalDate.parse("2019-12-01"); +// +// String formattedDate = date.format(formatter); +// System.out.println(formattedDate); +// } + + Deadline(String[] description) { + super(description[0].substring(9), description[1]); + } + + Deadline(String description, String eventDate, boolean isDone) { + super(description, eventDate, isDone); + formatDate(); + + } + + /** + * Method that formats the date into a standard format and stores it as local date. + */ + public void formatDate() { + this.date = LocalDate.parse(this.eventDate); + } + + /** + * Method that returns a standard formatted date. + * @return A formatted date time string. + */ + public String getDate() { + try { + return this.date.format(formatter); + } catch (Exception e) { + return this.eventDate; + } + } + + + @Override + public String getTaskType() { + return "D"; + } + + /** + * Returns formatted string of deadline. + * @return Formatted String representing deadline. + */ + @Override + public String toString() { + return String.format("[%s][%s] %s(by: %s)", this.getTaskType(), + this.getStatusIcon(), this.getDescription(), this.getDate()); + } + + +} diff --git a/src/main/java/DialogBox.java b/src/main/java/DialogBox.java new file mode 100644 index 0000000000..3b32eab1fc --- /dev/null +++ b/src/main/java/DialogBox.java @@ -0,0 +1,73 @@ +import java.io.IOException; +import java.util.Collections; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +/** + * An example of a custom control using FXML. + * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label + * containing text from the speaker. + */ +public class DialogBox extends HBox { + @FXML + private Label dialog; + @FXML + private ImageView displayPicture; + + private DialogBox(String text, Image img) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + dialog.setText(text); + displayPicture.setImage(img); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + Collections.reverse(tmp); + getChildren().setAll(tmp); + setAlignment(Pos.TOP_LEFT); + } + + + /** + * Retrieves the user dialog + * @param text Text to echo + * @param img PNG img of user + * @return Dialog box representing the user echo + */ + public static DialogBox getUserDialog(String text, Image img) { + return new DialogBox(text, img); + } + + /** + * Dialog box for Duke bot + * @param text Text output from Duke + * @param img PNG img of Duke + * @return DialogBox for Duke + */ + 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/Duke.java b/src/main/java/Duke.java index 5d313334cc..c5cb51178d 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,10 +1,115 @@ +import java.io.IOException; +import java.util.List; +import java.util.Scanner; + + +/** + * Driver class that takes in input and performs certain functions based on input. + */ public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); + + /** + * Helper method that processes the user input and returns corresponding for the + * user. GUI version. + * @param command User input, e.g. list, deadline... + * @param taskListStored Retrieved task list from local disk + * @return String to be displayed to user in dialogue box + * @throws IOException + */ + public String getOutput(String command, List taskListStored) throws IOException { + Parser parser = new Parser(); + Ui getInput = new Ui(); + TaskList taskList = new TaskList(taskListStored); + Storage storage = new Storage(); + storage.createDirectory(); + + String output = ""; + + if (parser.isStart(command)) { + output = getInput.printStart(); + } else if (parser.isList(command)) { + output = taskList.enumerateTasks(); + } else if (parser.isDone(command)) { + String[] taskToDelete = command.split("\\s+"); + output = taskList.markAsDone(Integer.parseInt(taskToDelete[1])); + } else if (parser.isTodo(command)) { + try { + Todo currentTask = new Todo(command.substring(5)); + taskList.addToTasks(currentTask); + output = taskList.logTask(currentTask); + } catch (StringIndexOutOfBoundsException indexError) { + output = getInput.outInvalidTodo(); + } + } else if (parser.isEvent(command)) { + try { + String[] splitString = command.split("/at"); + Event currentTask = new Event(splitString); + taskList.addToTasks(currentTask); + output = taskList.logTask(currentTask); + } catch (ArrayIndexOutOfBoundsException | StringIndexOutOfBoundsException indexError) { + output = getInput.outInvalidEvent(); + } + } else if (parser.isDeadline(command)) { + try { + String[] splitString = command.split("/by"); + Deadline currentTask = new Deadline(splitString); + taskList.addToTasks(currentTask); + output = taskList.logTask(currentTask); + } catch (ArrayIndexOutOfBoundsException | StringIndexOutOfBoundsException indexError) { + output = getInput.outInvalidDeadline(); + } + } else if (parser.isDelete(command)) { + try { + String[] splitString = command.split("\\s+"); + output = taskList.removeTask(Integer.parseInt(splitString[1])); + } catch (ArrayIndexOutOfBoundsException indexError) { + output = getInput.outInvalidDelete(); + } + } else if (parser.isFind(command)) { + try { + output = taskList.retrieveByKeyword(command); + } catch (ArrayIndexOutOfBoundsException indexError) { + output = getInput.outInvalidFind(); + } + } else if (parser.isBye(command)) { + output = getInput.outBye(); + } else if (parser.isHelp(command)) { + output = getInput.outHelp(); + } else { + // Command is not recognized + output = getInput.outNotRecognized(); + } + // Saving updated tasks to local disk + storage.saveFile(taskList.logAllTasks()); + + return output; } + + + /** + * Main class to take in user input. CLI version. + * @param params Standard user input params + */ + public static void main(String[] params) throws IOException { + Scanner sc = new Scanner(System.in); + + Storage storage = new Storage(); + // Starting string to prime the program + String command = Ui.primer; + + // Creating directory if it does not exist + storage.createDirectory(); + + // Loading file from directory if it exists + List tempList = storage.loadFile(); + + Duke duke = new Duke(); + + while (!command.startsWith("bye")) { + System.out.println(duke.getOutput(command, tempList)); + command = sc.nextLine(); + } + } + + } diff --git a/src/main/java/Event.java b/src/main/java/Event.java new file mode 100644 index 0000000000..67f8310dc7 --- /dev/null +++ b/src/main/java/Event.java @@ -0,0 +1,68 @@ +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class Event extends Task { + + + private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d/MM" + + "/yyyy HHmm"); + + private LocalDateTime time; + + /** + * Main Constructor. + * @param description Description of given task. + */ + Event(String description) { + super(description); + } + + Event(String[] description) { + super(description[0].substring(6), description[1]); + } + + /** + * Overloaded constructor to account for deadline and events that have timestamps. + * @param description Description of given task. + * @param eventDate Timestamp of given task. + */ + Event(String description, String eventDate) { + super(description, eventDate); + + try { + time = LocalDateTime.parse(eventDate, formatter); + } catch (Exception e) { + time = null; + } + } + + /** + * Overloaded constructor to set is done to a specified boolean value. + * @param description Description of given task. + * @param eventDate Date of task. + * @param isDone Boolean whether task has been completed. + */ + Event(String description, String eventDate, boolean isDone) { + super(description, eventDate, isDone); + } + + /** + * Retrieving the given task type. + * @return String giving the task type, in this case E. + */ + @Override + public String getTaskType() { + return "E"; + } + + /** + * String method of event. + * @return A formatted string of an event. + */ + @Override + public String toString() { + return String.format("[%s][%s] %s(at:%s)", this.getTaskType(), + this.getStatusIcon(), this.getDescription(), this.getEventDate()); + } + +} diff --git a/src/main/java/Launcher.java b/src/main/java/Launcher.java new file mode 100644 index 0000000000..1abfab13c1 --- /dev/null +++ b/src/main/java/Launcher.java @@ -0,0 +1,12 @@ +import javafx.application.Application; + +/** + * A launcher class to workaround classpath issues. + */ +public class Launcher { + + public static void main(String[] args) { + Application.launch(Main.class, args); + } + +} \ No newline at end of file diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 0000000000..d1306b327b --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,38 @@ +import java.io.IOException; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +/** + * Main class connecting the Duke application and the GUI + */ +public class Main extends Application { + + // Initializing Duke object. + private Duke duke = new Duke(); + + /** + * Overriding start method of application interface so that we can link the + * lauuncher to the main program logic in Duke.java + * @param stage Stage object that contains attributes that helps build the scene + */ + @Override + public void start(Stage stage) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml")); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + stage.setScene(scene); + fxmlLoader.getController().setDuke(duke); + stage.show(); + + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/MainWindow.java b/src/main/java/MainWindow.java new file mode 100644 index 0000000000..4322c4c489 --- /dev/null +++ b/src/main/java/MainWindow.java @@ -0,0 +1,66 @@ +import javafx.fxml.FXML; +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.layout.AnchorPane; +import javafx.scene.layout.VBox; + +import java.io.IOException; +import java.util.List; + +/** + * Controller for MainWindow. Provides the layout for the other controls. + */ +public class MainWindow extends AnchorPane { + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogContainer; + @FXML + private TextField userInput; + @FXML + private Button sendButton; + + private Duke duke; + + private Image userImage = new Image(this.getClass().getResourceAsStream("/images" + + "/User.PNG")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images" + + "/Bot.PNG")); + + + /** + * Initialize method for fxml. + */ + @FXML + public void initialize() { + Ui ui = new Ui(); + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + Label opener = new Label(ui.printStart()); + dialogContainer.getChildren().add(opener); + } + + 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. + */ + @FXML + private void handleUserInput() throws IOException { + assert userImage != null && dukeImage != null; + Storage storage = new Storage(); + List taskList = storage.loadFile(); + String input = userInput.getText(); + String response = duke.getOutput(input, taskList); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(input, userImage), + DialogBox.getDukeDialog(response, dukeImage) + ); + userInput.clear(); + } +} \ No newline at end of file diff --git a/src/main/java/Parser.java b/src/main/java/Parser.java new file mode 100644 index 0000000000..1a5b6a4f48 --- /dev/null +++ b/src/main/java/Parser.java @@ -0,0 +1,45 @@ +public class Parser { + + public boolean stillHaveCommands(String command) { + return !command.equalsIgnoreCase("bye"); + } + + public boolean isStart(String command) { + return command.equalsIgnoreCase("hello"); + } + + public boolean isList(String command) { + return command.startsWith("list"); + } + + public boolean isTodo(String command) { + return command.startsWith("todo"); + } + + public boolean isHelp(String command) {return command.startsWith("help");} + + public boolean isDeadline(String command) { + return command.startsWith("deadline"); + } + + public boolean isEvent(String command) { + return command.startsWith("event"); + } + + public boolean isDelete(String command) { + return command.startsWith("delete"); + } + + public boolean isFind(String command) { + return command.startsWith("find"); + } + + public boolean isBye(String command) { + return command.startsWith("bye"); + } + + public boolean isDone(String command) { + return command.startsWith("done"); + } + +} diff --git a/src/main/java/Storage.java b/src/main/java/Storage.java new file mode 100644 index 0000000000..4335091156 --- /dev/null +++ b/src/main/java/Storage.java @@ -0,0 +1,100 @@ +import com.sun.scenario.effect.impl.sw.java.JSWBlend_EXCLUSIONPeer; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Storage { + + // Source folder + private final static String sourceFolder = "data"; + // Name of text file to store tasks + private final static String sourceFile = "/Duke.txt"; + + /** + * Creating directory if it does not exist. + */ + public void createDirectory() { + File directory = new File(sourceFolder); + if (!directory.exists()) { + boolean success = directory.mkdir(); + if (!success) { + System.out.println("Directory creation was unsuccessful. Please " + + "manually create it."); + } + } + } + + /** + * Overwrites content to save new content to disk. + */ + public void saveFile(String tasksToSave) throws IOException { + try { + BufferedWriter out = new BufferedWriter(new FileWriter(sourceFolder + sourceFile, + false)); + out.write(tasksToSave); + out.flush(); + } catch (Exception ignored) { + + } + + } + + public static void main(String[] args) throws IOException { + Storage storage = new Storage(); + storage.loadFile(); + } + + /** + * Method that loads primary file from local disk. Takes in the stored string in + * text file stored locally, and parses it into a List containing tasks. + * @return Task List + * @throws IOException + */ + public List loadFile() throws IOException { + File directory = new File(sourceFolder + sourceFile); + List taskList = new ArrayList<>(); + try { + if (directory.exists()) { + BufferedReader br = + new BufferedReader(new FileReader(sourceFolder + sourceFile)); + String line; + while ((line = br.readLine()) != null) { + + String[] eachTask = line.split("[|]"); + String taskType = eachTask[0]; + assert eachTask.length >= 2; + boolean isDone = eachTask[1].contains("X"); + assert eachTask.length >= 3; + String description = eachTask[2]; + + if (taskType.equals("T")) { + taskList.add(new Todo(description, isDone)); + } else if (taskType.equals("E")) { + assert eachTask.length >=4; + taskList.add(new Event(description, eachTask[3], isDone)); + } else { + assert eachTask.length >=4; + String eventDate = eachTask[eachTask.length - 1].replaceAll("\\s+", + ""); + taskList.add(new Deadline(description, eventDate, isDone)); + } + } + } + + } catch (Exception e) { + return taskList; + } + + return taskList; + } + + + +} diff --git a/src/main/java/Task.java b/src/main/java/Task.java new file mode 100644 index 0000000000..65dffa23de --- /dev/null +++ b/src/main/java/Task.java @@ -0,0 +1,79 @@ +import java.time.LocalDate; + +public class Task { + + public final String description; + public final String eventDate; + public boolean isDone; + public LocalDate date; + + /** + * Main constructor that accepts a description of the task, and by default + * assigns a false value to the undone task. + * @param description Description of task + */ + public Task(String description) { + this.description = description; + this.isDone = false; + this.eventDate = ""; + this.date = null; + } + + + /** + * Overloaded method to change the isDone parameter. + * @param description Description of task + * @param isDone Boolean flag representing whether event is completed. + */ + public Task(String description, boolean isDone) { + this.description = description; + this.isDone = isDone; + this.eventDate = ""; + this.date = null; + } + + /** + * Overloaded method to mark a task as done. + * @param description Description of method + * @param eventDate Boolean flag indicating whether task is done + */ + public Task(String description, String eventDate) { + this.description = description; + this.eventDate = eventDate; + this.isDone = false; + this.date = null; + } + + /** + * Overloaded method to change the isDone parameter and also accepts date. + * @param description Description of task + * @param isDone Boolean flag representing whether event is completed. + */ + public Task(String description, String eventDate, boolean isDone) { + this.description = description; + this.eventDate = eventDate; + this.isDone = isDone; + this.date = null; + } + + public String getTaskType() { + return ""; + }; + + public String getStatusIcon() { + return isDone ? "X" : ""; + } + + public String getDescription() { + return this.description; + } + + public String getEventDate() { + return this.eventDate; + } + + public void markAsDone() { + this.isDone = true; + } + +} diff --git a/src/main/java/TaskList.java b/src/main/java/TaskList.java new file mode 100644 index 0000000000..87773d023e --- /dev/null +++ b/src/main/java/TaskList.java @@ -0,0 +1,142 @@ +import java.util.ArrayList; +import java.util.List; + +public class TaskList { + + public List taskList; + + TaskList(List taskList) { + this.taskList = taskList; + } + + /** + * Prints the statements showing this task has been added to list. + * @param currentTask Current task. + */ + protected String logTask(Task currentTask) { + return String.format("Got it. I've added this task: " + currentTask + + String.format("\nNow you have %d tasks in the list", taskList.size())); + + } + + /** + * Method that logs all existing tasks and returns a string that can be logged + * into the local disk in data/Duke.txt. + * @return A string containing all existing tasks. + */ + protected String logAllTasks() { + StringBuilder out = new StringBuilder(); + for (Task eachTask : taskList) { + String taskType = eachTask.getTaskType(); + out.append(taskType).append("|"); + out.append(eachTask.getStatusIcon()).append("|"); + if (taskType.equals("D") || taskType.equals("E")) { + out.append(eachTask.getDescription()).append("|"); + out.append(eachTask.getEventDate()); + } else { + out.append(eachTask.getDescription()); + } + out.append("\n"); + } + return out.toString(); + } + + /** + * Adds current task to list of tasks. + * @param currentTask Current task. + */ + protected void addToTasks(Task currentTask) { + taskList.add(currentTask); + } + + /** + * Enumerates all tasks in the list using 1-based indexing. + * @return String representing all existing tasks with 1-based indexing + */ + protected String enumerateTasks() { + + StringBuilder res = new StringBuilder("Here are the tasks in your list:"); + + int counter = 1; + for (Task eachTask : taskList) { + res.append(String.format("\n%d. %s", counter, eachTask)); + counter++; + } + assert counter == taskList.size(); + return res.toString(); + } + + /** + * Marks task as done based on 1-based indexing. + * @param index Given index of task + */ + protected String markAsDone(int index) { + // Retrieving task + Task givenTask = taskList.get(index - 1); + givenTask.markAsDone(); + String res = "Nice! I've marked this task as done:\n"; + + res += givenTask.toString(); + return res; + } + + /** + * Removes respective task in the list (1-based indexing). + * @param index Index of task to remove + */ + protected String removeTask(int index) { + Task removedTask = taskList.remove(index - 1); + + String res = "Noted. I've removed this task:\n" + " " + removedTask; + + res += String.format("\nNow you have %d tasks in your list.", + taskList.size()); + return res; + } + + /** + * Retrieve task(s) by keyword match so long as description contains the word. + * @param command Keyword to match + * @return A string line-spaced by each task that has the keyword + */ + protected String retrieveByKeyword(String command) { + int counter = 1; + String[] keywordSearch = command.split("\\s+"); + assert keywordSearch.length >=2; + String keyword = keywordSearch[1]; + String res = "Here are the matching tasks in your list:"; + int lenSearch = keywordSearch.length; + for (Task eachTask : taskList) { + if (lenSearch == 2) { + // default partial search case sensitive + if (eachTask.getDescription().contains(keyword)) { + res += String.format("\n%d. %s", counter, eachTask); + counter++; + } + } else if (lenSearch == 3) { + String searchType = keywordSearch[2]; + if (searchType.equalsIgnoreCase("p")) { + if (eachTask.getDescription().contains(keyword)) { + res += String.format("\n%d. %s", counter, eachTask); + counter++; + } + } else if (searchType.equalsIgnoreCase("pi")) { + // Case insensitive partial search + if (eachTask.getDescription().toLowerCase().contains(keyword.toLowerCase())) { + res += String.format("\n%d. %s", counter, eachTask); + counter++; + } + } else if (searchType.equalsIgnoreCase("f")) { + // full search must be exactly as the task description + if (eachTask.getDescription().equals(keyword)) { + res += String.format("\n%d. %s", counter, eachTask); + counter++; + } + } + } + + } + return res; + } + +} diff --git a/src/main/java/Todo.java b/src/main/java/Todo.java new file mode 100644 index 0000000000..216d486880 --- /dev/null +++ b/src/main/java/Todo.java @@ -0,0 +1,33 @@ +public class Todo extends Task { + + /** + * Main constructor. + * @param description Description of task. + */ + Todo(String description) { + super(description); + } + + Todo(String description, boolean isDone) { + super(description, isDone); + } + + /** + * Getter method that retrieves the type of the task. + * @return String representing task type. + */ + @Override + public String getTaskType() { + return "T"; + } + + /** + * Standard to string method. + * @return Formatted string of a task. + */ + @Override + public String toString() { + return String.format("[%s][%s] %s ", this.getTaskType(), + this.getStatusIcon(), this.getDescription()); + } +} diff --git a/src/main/java/Ui.java b/src/main/java/Ui.java new file mode 100644 index 0000000000..f46c9b0162 --- /dev/null +++ b/src/main/java/Ui.java @@ -0,0 +1,65 @@ +public class Ui { + + + public static final String primer = "Hello"; + + public void printStarter() { + System.out.println("Hello! I'm Duke"); + System.out.println("What can I do for you?"); + } + + public String printStart() { + String res = String.format("Hello! I'm Duke" + "\n" + "What can I do for " + + "you?\n"); + + res += outHelp(); + return res; + } + + public String outHelp() { + final String commandTypes = String.format("Here are the list of all commands:" + + " \n(Type help again to repeat these commands)\n\n" + + "list - lists all existing tasks\n" + + "todo [insert task] - creates a todo task\n" + + "deadline [insert deadline] - creates a deadline\n" + + "event [insert event] - creates an event\n" + + "delete [insert index] - deletes event based on index\n" + + "find [insert keyword] [search type(optional)] - finds tasks " + + "corresponding\n to keyword\n" + + "done [insert index] - marks task as done"); + return commandTypes; + } + + public String outBye() { + return "Bye. Hope to see you again soon!"; + } + + public String outNotRecognized() { + return ":( OOPS!!! I'm sorry, but I don't know what that means :-("; + } + + public String outInvalidDeadline() { + return ":( OOPS!!! The description of a deadline " + + "cannot be empty."; + } + + public String outInvalidEvent() { + return ":( OOPS!!! The description of an event cannot be empty."; + } + + public String outInvalidTodo() { + return ":( OOPS!!! The description of a todo cannot be empty."; + } + + public String outInvalidDelete() { + return ":( OOPS!!! Please ensure that you have correctly indicated the index " + + "of the item you want to find."; + } + + public String outInvalidFind() { + return ":( OOPS!!! Please ensure that you have correctly indicated the " + + "keyword of the task(s) you want to search for."; + } + + +} diff --git a/src/main/resources/css/style.css b/src/main/resources/css/style.css new file mode 100644 index 0000000000..c6609dc0c7 --- /dev/null +++ b/src/main/resources/css/style.css @@ -0,0 +1,19 @@ +.background { + -fx-background-image: url('../images/background.png'); + -fx-background-size: contain; + +} + +.messageWindow { + -fx-border-width: 10.0; + -fx-border-color: green; + -fx-background-color: #F4E7B0; + -fx-background-radius: 15; + -fx-padding: 30px; + -fx-border-insets: 5px; + -fx-border-radius: 5; + -fx-background-insets: 5px; + -fx-text-fill: black; + -fx-font-weight: bold; + -fx-effect: dropshadow(gaussian, rgba(0, 0, 0.1, 0.2), 10, 0.5, 0.0, 0.0); +} diff --git a/src/main/resources/images/Bot.PNG b/src/main/resources/images/Bot.PNG new file mode 100644 index 0000000000..3be1eb888f Binary files /dev/null and b/src/main/resources/images/Bot.PNG differ diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png new file mode 100644 index 0000000000..d893658717 Binary files /dev/null and b/src/main/resources/images/DaDuke.png differ diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png new file mode 100644 index 0000000000..3c82f45461 Binary files /dev/null and b/src/main/resources/images/DaUser.png differ diff --git a/src/main/resources/images/User.PNG b/src/main/resources/images/User.PNG new file mode 100644 index 0000000000..5000001c9e Binary files /dev/null and b/src/main/resources/images/User.PNG differ diff --git a/src/main/resources/images/background.png b/src/main/resources/images/background.png new file mode 100644 index 0000000000..1c2f4004ca Binary files /dev/null and b/src/main/resources/images/background.png differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..a8a340f3fc --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + \ 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..b10247e2a3 --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,24 @@ + + + + + + + + + + + +