diff --git a/.gitignore b/.gitignore index f69985ef1f..f411567868 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ bin/ /text-ui-test/ACTUAL.txt text-ui-test/EXPECTED-UNIX.TXT +tasklist.txt diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..fc9e574e35 --- /dev/null +++ b/build.gradle @@ -0,0 +1,59 @@ +plugins { + id 'java' + id 'application' + id 'com.github.johnrengelman.shadow' version '5.1.0' +} + +repositories { + mavenCentral() +} + +dependencies { + String javaFxVersion = '11' + + // Junit + 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' + + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' + +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + +application { + mainClassName = "duke.Launcher" +} + +shadowJar { + archiveBaseName = "duke" + archiveClassifier = null +} + + +run{ + standardInput = System.in +} diff --git a/docs/README.md b/docs/README.md index 8077118ebe..a61cc1bd5a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,28 +2,61 @@ ## Features -### Feature-ABC +### Feature - Add Todo Task -Description of the feature. +Add a todo task to the list -### Feature-XYZ +Example of usage: +`todo (task)` +`t (task)` -Description of the feature. +### Feature - Add Deadline Task -## Usage +Add a deadline task to the list -### `Keyword` - Describe action +Example of usage: +`deadline (task) /by (date in YYYY-MM-DD format)` +`d (task) /by (date in YYYY-MM-DD format)` -Describe the action and its outcome. +### Feature - Add Event Task -Example of usage: +Add an event task to the list -`keyword (optional arguments)` +Example of usage: +`event (task) /at (event location)` +`e (task) /at (event location)` -Expected outcome: +### Feature - List tasks -Description of the outcome. +Lists the current tasklist + +Example of usage: +`list` +`l` + +### Feature - Mark and unmark tasks + +Mark and unmark tasks as done + +Example of usage: +`mark (index of task in list)` +`unmark (index of task in list)` +`m (index)` +`um (index)` + +### Feature - Remove tasks + +Remove tasks from list + +Example of usage: +`delete (index of task in list)` +`del (index)` + +### Feature - Find tasks + +Find tasks in list based on matching text + +Example of usage: +`find (text to search)` +`f (text)` -``` -expected output -``` diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..c942509178 Binary files /dev/null and b/docs/Ui.png differ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..f3d88b1c2f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..b7c8c5dbf5 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..2fe81a7d95 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..62bd9b9cce --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,103 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java 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..2c9a9745c5 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: duke.Duke + diff --git a/src/main/java/duke/AddTaskCommand.java b/src/main/java/duke/AddTaskCommand.java new file mode 100644 index 0000000000..ad238808d3 --- /dev/null +++ b/src/main/java/duke/AddTaskCommand.java @@ -0,0 +1,65 @@ +package duke; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * Generates the correct type of task and adds it to the task list + */ + +public class AddTaskCommand extends Command { + + private String input; // Details of the task + private String type; // Type of task + private boolean isComplete = false; + + /** + * Constructor for AddTaskCommand, without isComplete flag + * + * @param input User input + * @param type Type of command + */ + public AddTaskCommand(String input, String type) { + this.input = input; + this.type = type; + } + + /** + * Constructor for AddTaskCommand, with isComplete flag + * + * @param input User input + * @param type Type of command + * @param isComplete Whether the task should be created with the task marked as done already + */ + public AddTaskCommand(String input, String type, boolean isComplete) { + this.input = input; + this.type = type; + this.isComplete = isComplete; + } + + + @Override + public void execute() { + Task newTask; + if (this.type.equals("todo")) { + newTask = new TodoTask(this.input); + } else if (this.type.equals("deadline")) { + newTask = new DeadlineTask(this.input); + } else if (this.type.equals("event")) { + newTask = new EventTask(this.input); + } else { + newTask = null; + } + + // Set indicated completion state + newTask.isComplete = this.isComplete; + + // Adding task to tasklist + TaskList.taskList.add(newTask); + + Ui.printAddTask(newTask); + } +} diff --git a/src/main/java/duke/ByeCommand.java b/src/main/java/duke/ByeCommand.java new file mode 100644 index 0000000000..1051a03fb7 --- /dev/null +++ b/src/main/java/duke/ByeCommand.java @@ -0,0 +1,15 @@ +package duke; + +/** + * Prints bye message, terminating command for Duke.main() + */ +public class ByeCommand extends Command { + + final String EXIT = "Duke terminated"; + + @Override + public void execute() { + Ui.printExit(); + System.exit(2); + } +} diff --git a/src/main/java/duke/Command.java b/src/main/java/duke/Command.java new file mode 100644 index 0000000000..55b7b43bcb --- /dev/null +++ b/src/main/java/duke/Command.java @@ -0,0 +1,6 @@ +package duke; + +public abstract class Command { + public abstract void execute(); +} + diff --git a/src/main/java/duke/CustomTask.java b/src/main/java/duke/CustomTask.java new file mode 100644 index 0000000000..6f29dba84b --- /dev/null +++ b/src/main/java/duke/CustomTask.java @@ -0,0 +1,17 @@ +package duke; + +public class CustomTask extends Task { + /** + * Constructor for a CustomTask, used in repopulating the task array during a tasklist load + * + * @param type Type of task + * @param isComplete Whether the task is done + * @param input User input (includes task type and completion symbols) + */ + public CustomTask(String type, boolean isComplete, String input) { + super(input); + this.type = type; + this.description = this.input; // Updates description of class + this.isComplete = isComplete; + } +} diff --git a/src/main/java/duke/DeadlineTask.java b/src/main/java/duke/DeadlineTask.java new file mode 100644 index 0000000000..70efe05e93 --- /dev/null +++ b/src/main/java/duke/DeadlineTask.java @@ -0,0 +1,75 @@ +package duke; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +public class DeadlineTask extends Task { + + LocalDate date; + + /** + * Constructor for DeadlineTask + * + * @param input User input + */ + public DeadlineTask(String input) { + super(input); + this.type = "deadline"; + this.date = this.getDate(); + this.updateDescription(); + } + + /** + * Format DeadlineTask description into display format for Duke + */ + private void updateDescription() { + this.description += this.getInfo(); + this.description += "(by: "; + this.description += this.date.format(DateTimeFormatter.ofPattern("d MMM yyyy")); + this.description += ")"; + } + + /** + * Find the index from which datetime information starts + * + * @return Integer index + */ + private int findDeadlineIndex() { + int index = this.input.indexOf("/by "); // "/by" as delimiting character for events + try { + if (index == -1) { + throw new DukeException(); + } + } catch (DukeException e) { + System.out.println("No /by date specified."); + System.exit(2); + } + return index; + } + + /** + * Gets the task information + * + * @return Task information + */ + private String getInfo() { + int index = this.findDeadlineIndex(); + String info = this.input.substring(0, index); + + return info; + } + + /** + * Gets the deadline date + * + * @return Deadline date string + */ + private LocalDate getDate() { + int index = this.findDeadlineIndex() + 4; + String date = this.input.substring(index); + LocalDate localDate = LocalDate.parse(date); + + return localDate; + } + +} diff --git a/src/main/java/duke/DeleteCommand.java b/src/main/java/duke/DeleteCommand.java new file mode 100644 index 0000000000..7bc790c533 --- /dev/null +++ b/src/main/java/duke/DeleteCommand.java @@ -0,0 +1,32 @@ +package duke; + +import java.util.ArrayList; + +/** + * Deletes the indicated task + */ +public class DeleteCommand extends Command { + private String input; // Parameters: 1 + public ArrayList Storage = TaskList.taskList; + private int index; // Index of target task + + /** + * Constructor for DeleteCommand + * + * @param i Index of task in the tasklist array to delete + */ + public DeleteCommand(String i) { + this.input = i; + index = Integer.parseInt(input) - 1; // -1 because list starts at 1 while indexes start at 0 + } + + @Override + public void execute() { + int index = Integer.parseInt(input) - 1; // -1 because list starts at 1 while indexes start at 0 + // Console prints + Task task = TaskList.taskList.get(index); + Ui.printDeleteTask(task); // Print before delete + TaskList.taskList.remove(index); + + } +} \ No newline at end of file diff --git a/src/main/java/duke/DialogBox.java b/src/main/java/duke/DialogBox.java new file mode 100644 index 0000000000..e399dd149c --- /dev/null +++ b/src/main/java/duke/DialogBox.java @@ -0,0 +1,47 @@ +package duke; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; + +public class DialogBox extends HBox { + + private Label text; + private ImageView displayPicture; + + public DialogBox(Label l, ImageView iv) { + text = l; + displayPicture = iv; + + text.setWrapText(true); + displayPicture.setFitWidth(100.0); + displayPicture.setFitHeight(100.0); + + this.setAlignment(Pos.TOP_RIGHT); + this.getChildren().addAll(text, displayPicture); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + this.setAlignment(Pos.TOP_LEFT); + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + FXCollections.reverse(tmp); + this.getChildren().setAll(tmp); + } + + public static DialogBox getUserDialog(Label l, ImageView iv) { + return new DialogBox(l, iv); + } + + public static DialogBox getDukeDialog(Label l, ImageView iv) { + var db = new DialogBox(l, iv); + db.flip(); + return db; + } +} \ No newline at end of file diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java new file mode 100644 index 0000000000..63d4fe95fb --- /dev/null +++ b/src/main/java/duke/Duke.java @@ -0,0 +1,62 @@ +package duke; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +public class Duke { + + + public static void main() { + // Init UI + Ui ui = new Ui(); + + // Init file if it is not there + File f = new File("./tasklist.txt"); + if (!f.isFile()) { + try { + f.createNewFile(); + } catch (IOException e) { + System.out.println(e); + } + } + + assert(f.isFile()) : "File should exist"; + + // Init saved tasks + try { + Storage.loadTasks(); + } catch (IOException e) { + e.printStackTrace(); + } + + // Reading and processing inputs + Parser Parser = new Parser(); + String nextLine = ui.getNextLine(); + Command nextCommand = Parser.makeCommand(nextLine); + nextCommand.execute(); + + + try { + Storage.saveTaskList(); + TaskList.taskList = new ArrayList(); // Reset task list + assert (TaskList.taskList.size() == 0) : "New tasklist should be empty"; + } catch (IOException e) { + e.printStackTrace(); + } + } + + + + /** + * Formats a single line with a new line + * + * @param input Input string + * @return String with newline + */ + public static String newLine(String input) { + String output = input + "\n"; + return output; + } + +} diff --git a/src/main/java/duke/DukeException.java b/src/main/java/duke/DukeException.java new file mode 100644 index 0000000000..78537b2944 --- /dev/null +++ b/src/main/java/duke/DukeException.java @@ -0,0 +1,4 @@ +package duke; + +public class DukeException extends Exception { +} diff --git a/src/main/java/duke/DukeGUI.java b/src/main/java/duke/DukeGUI.java new file mode 100644 index 0000000000..393d034260 --- /dev/null +++ b/src/main/java/duke/DukeGUI.java @@ -0,0 +1,139 @@ +package duke; + +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.control.Label; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; + + +public class DukeGUI extends Application { + + //@@author {nus-cs2103-AY2122S2}-reused + // User images + private Image user = new Image(this.getClass().getResourceAsStream("/DaUser.png")); + private Image duke = new Image(this.getClass().getResourceAsStream("/DaDuke.png")); + + private ScrollPane scrollPane; + private VBox dialogContainer; + private TextField userInput; + private Button sendButton; + private Scene scene; + + // Stream for Duke to output to and DukeGUI to read from + private static ByteArrayOutputStream baos = new ByteArrayOutputStream(); + public static PrintStream outputStream = new PrintStream(baos); + + public static void main() { + System.out.println("Working Directory = " + System.getProperty("user.dir")); + } + + @Override + public void start(Stage stage) { + + //Step 1. Setting up required components + + //The container for the content of the chat to scroll. + scrollPane = new ScrollPane(); + dialogContainer = new VBox(); + scrollPane.setContent(dialogContainer); + + userInput = new TextField(); + sendButton = new Button("Send"); + + AnchorPane mainLayout = new AnchorPane(); + mainLayout.getChildren().addAll(scrollPane, userInput, sendButton); + + scene = new Scene(mainLayout); + + stage.setScene(scene); + stage.show(); + + //Step 2. Formatting the window to look as expected + stage.setTitle("Duke"); + stage.setResizable(false); + stage.setMinHeight(600.0); + stage.setMinWidth(400.0); + + mainLayout.setPrefSize(400.0, 600.0); + + scrollPane.setPrefSize(385, 535); + scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS); + + scrollPane.setVvalue(1.0); + scrollPane.setFitToWidth(true); + + // You will need to import `javafx.scene.layout.Region` for this. + dialogContainer.setPrefHeight(Region.USE_COMPUTED_SIZE); + + userInput.setPrefWidth(325.0); + + sendButton.setPrefWidth(55.0); + + AnchorPane.setTopAnchor(scrollPane, 1.0); + + AnchorPane.setBottomAnchor(sendButton, 1.0); + AnchorPane.setRightAnchor(sendButton, 1.0); + + AnchorPane.setLeftAnchor(userInput, 1.0); + AnchorPane.setBottomAnchor(userInput,1.0); + + //Step 3. Add functionality to handle user input. + sendButton.setOnMouseClicked((event) -> { + try { + handleUserInput(); + } catch (IOException e) { + e.printStackTrace(); + } + }); + + userInput.setOnAction((event) -> { + try { + handleUserInput(); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + + private void handleUserInput() throws IOException { + String userInputText = userInput.getText(); + Label userText = new Label(userInputText); + Label dukeText = new Label(getResponse(userInputText)); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(userText, new ImageView(user)), + DialogBox.getDukeDialog(dukeText, new ImageView(duke)) + ); + userInput.clear(); + } + //@@author + + private String getResponse(String input) throws IOException { + // Let Duke read from System.in + ByteArrayInputStream byteInput = new ByteArrayInputStream(input.getBytes()); + System.setIn(byteInput); + Duke.main(); + + // Let DukeGUI read from System.out + String output = DukeGUI.baos.toString(); + DukeGUI.baos = new ByteArrayOutputStream(); + DukeGUI.outputStream = new PrintStream(DukeGUI.baos); + + return output; + + } +} \ No newline at end of file diff --git a/src/main/java/duke/EventTask.java b/src/main/java/duke/EventTask.java new file mode 100644 index 0000000000..5460d47333 --- /dev/null +++ b/src/main/java/duke/EventTask.java @@ -0,0 +1,69 @@ +package duke; + +public class EventTask extends Task { + /** + * Constructor for EventTask + * + * @param input User input + */ + public EventTask(String input) { + super(input); + this.type = "event"; + this.updateDescription(); + } + + /** + * Format EventTask description into display format for Duke + */ + private void updateDescription() { + this.description += this.getInfo(); + this.description += "(at: "; + this.description += this.getDate(); + this.description += ")"; + } + + /** + * Find the index from which datetime information starts + * + * @return Integer index + */ + private int findEventIndex() { + int index = this.input.indexOf("/at "); // "/at" as delimiting character for events + try { + if (index == -1) { + throw new DukeException(); + } + } catch (DukeException e) { + System.out.println("No /at date specified."); + System.exit(2); + } + return index; + } + + /** + * Gets the task information + * + * @return Task information + */ + private String getInfo() { + int index = this.findEventIndex(); + String info = this.input.substring(0, index); + + return info; + } + + /** + * Gets the deadline date + * + * @return Deadline date string + */ + private String getDate() { + int index = this.findEventIndex() + 4; // Offset of the string "/at " + String date = this.input.substring(index); + + return date; + } + +} + + diff --git a/src/main/java/duke/FindCommand.java b/src/main/java/duke/FindCommand.java new file mode 100644 index 0000000000..0ef22add78 --- /dev/null +++ b/src/main/java/duke/FindCommand.java @@ -0,0 +1,37 @@ +package duke; + +import java.lang.reflect.Array; +import java.util.ArrayList; + +public class FindCommand extends Command { + private ArrayList taskList = TaskList.taskList; + private String input; + + public FindCommand(String input) { + this.input = input; + } + + @Override + public void execute() { + ArrayList foundTasks = findTasks(); + Ui.printFindTask(foundTasks); + } + + /** + * Finds tasks in the arraylist that match the search term + * + * @return Arraylist of found tasks + */ + private ArrayList findTasks() { + ArrayList foundTasks = new ArrayList<>(); + for (Task t : taskList) { + if (t.getDescription().contains(input)) { + foundTasks.add(t); + } + } + + return foundTasks; + } + + +} diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java new file mode 100644 index 0000000000..979747b48b --- /dev/null +++ b/src/main/java/duke/Launcher.java @@ -0,0 +1,10 @@ +package duke; + +import javafx.application.Application; + +public class Launcher { + public static void main (String[] args) { + Application.launch(DukeGUI.class, args); + DukeGUI.main(); + } +} diff --git a/src/main/java/duke/ListCommand.java b/src/main/java/duke/ListCommand.java new file mode 100644 index 0000000000..4e96ace3b0 --- /dev/null +++ b/src/main/java/duke/ListCommand.java @@ -0,0 +1,45 @@ +package duke; + +import java.util.ArrayList; + +/** + * Prints the current list of tasks + */ +public class ListCommand extends Command { + @Override + public void execute() { + ArrayList Storage = TaskList.taskList; + + // Formatting task list to be printed + String formattedTaskList = formatTaskList(TaskList.taskList); + Ui.printTaskList(formattedTaskList); + } + + /** + * Formats the task list for readability + * + * @param taskList ArrayList to be formatted + * @return formattedTaskList Formatted task list + */ + private String formatTaskList(ArrayList taskList) { + int indexCounter = 1; + String formattedTaskList = ""; + int taskListSize = taskList.size(); + + if (taskListSize == 0) { + return "No tasks in list"; + } + + for (Task i : taskList) { + String item = String.valueOf(indexCounter) + ". " + i.toString(); + formattedTaskList += Duke.newLine(item); + indexCounter += 1; + } + + formattedTaskList = formattedTaskList.substring(0, formattedTaskList.length() - 1); // Remove last \n sequence + + return formattedTaskList; + } + + +} diff --git a/src/main/java/duke/MarkCommand.java b/src/main/java/duke/MarkCommand.java new file mode 100644 index 0000000000..2790b41e1d --- /dev/null +++ b/src/main/java/duke/MarkCommand.java @@ -0,0 +1,31 @@ +package duke; + +import java.util.ArrayList; + +/** + * Marks the indicated task as done + */ +public class MarkCommand extends Command { + private String input; // Parameters: 1 + public ArrayList taskList = TaskList.taskList; + private int index; // Index of target task + + /** + * Constructor for MarkCommand + * + * @param i Index of task in tasklist array to be marked as complete + */ + public MarkCommand(String i) { + this.input = i; + index = Integer.parseInt(input) - 1; // -1 because list starts at 1 while indexes start at 0 + } + + @Override + public void execute() { + Task task = taskList.get(index); + task.markAsDone(); + + Ui.printMarkTask(task); + + } +} diff --git a/src/main/java/duke/Parser.java b/src/main/java/duke/Parser.java new file mode 100644 index 0000000000..d92e7ad717 --- /dev/null +++ b/src/main/java/duke/Parser.java @@ -0,0 +1,75 @@ +package duke; + +/** + * Decides which type of command to generate given user input + */ +public class Parser { + + /** + * Extracts the command portion of the input provided by the user (usually first word) + * + * @param input Input from the user + * @return One word string + */ + private String isolateCommand(String input) { + int whiteSpaceIndex = input.indexOf(" "); // Index of first whitespace + if (whiteSpaceIndex == -1) { // If the input string has no whitespaces (i.e. one word) + return input; + } + return input.substring(0, whiteSpaceIndex); + } + + /** + * Extracts the command parameters portion of the input provided by the user (stuff after first word) + * + * @param input Input from the user + * @return Multiple-word string + */ + private String isolateParameters(String input) { + int whiteSpaceIndex = input.indexOf(" "); // Index of first whitespace + + try { + return input.substring(whiteSpaceIndex + 1); // Return the rest of the word, starting from after whitespace + } catch (Exception e) { + return null; + } + } + + /** + * Generates the appropriate command type given the input + * + * @param input Input from the user + * @return Value of parent type Command + */ + public Command makeCommand(String input) { + String commandWord = isolateCommand(input); + String commandParameters = isolateParameters(input); + try { + if (commandWord.equals("bye")) { + return new ByeCommand(); + } else if (commandWord.equals("list") || commandWord.equals("l")) { + return new ListCommand(); + } else if (commandWord.equals("mark") || commandWord.equals("m")) { + return new MarkCommand(commandParameters); + } else if (commandWord.equals("unmark") || commandWord.equals("um")) { + return new UnmarkCommand(commandParameters); + } else if (commandWord.equals("todo") || commandWord.equals("t")) { + return new AddTaskCommand(commandParameters, "todo"); + } else if (commandWord.equals("deadline") || commandWord.equals("d")) { + return new AddTaskCommand(commandParameters, "deadline"); + } else if (commandWord.equals("event") || commandWord.equals("e")) { + return new AddTaskCommand(commandParameters, "event"); + } else if (commandWord.equals("delete") || commandWord.equals("del")) { + return new DeleteCommand(commandParameters); + } else if (commandWord.equals("find") || commandWord.equals("f")) { + return new FindCommand(commandParameters); + } else { + throw new DukeException(); + } + + } catch (DukeException e) { + System.out.println("Invalid command given"); + return new ByeCommand(); + } + } +} diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/Storage.java new file mode 100644 index 0000000000..a856d30225 --- /dev/null +++ b/src/main/java/duke/Storage.java @@ -0,0 +1,53 @@ +package duke; + +import java.io.*; + +public class Storage { + /** + * Loads existing tasks into the tasklist array (for use at start of program) + */ + public static void loadTasks() throws IOException { + BufferedReader reader = new BufferedReader(new FileReader("./tasklist.txt")); + String currentLine; + while ((currentLine = reader.readLine()) != null) { + // Getting task type + String taskType = currentLine.substring(0, 3); // First 3 characters eg. "[T]" + if (taskType.equals("[T]")) { + taskType = "todo"; + } else if (taskType.equals("[D]")) { + taskType = "deadline"; + } else { + taskType = "event"; + } + + // Getting completion state + String completion = currentLine.substring(3, 6); // 4th - 6th characters eg. "[X]" + boolean isComplete = completion.equals("[X]"); + + // Getting description string (already formatted) + String description = currentLine.substring(7); + + CustomTask task = new CustomTask(taskType, isComplete, description); + TaskList.taskList.add(task); + + } + reader.close(); + + } + + /** + * Writes the current task list into tasklist.txt + */ + public static void saveTaskList() throws IOException { + File f = new File("./tasklist.txt"); + f.delete(); // Delete current copy + f.createNewFile(); + + FileWriter writer = new FileWriter(f); + for (Task task : TaskList.taskList) { + writer.write(task.toString() + "\n"); + } + + writer.close(); + } +} diff --git a/src/main/java/duke/Task.java b/src/main/java/duke/Task.java new file mode 100644 index 0000000000..1e76b5d3d6 --- /dev/null +++ b/src/main/java/duke/Task.java @@ -0,0 +1,80 @@ +package duke; + +public class Task { + protected String input; + public boolean isComplete = false; + protected String type; + protected String description = ""; // Display format for Duke + + /** + * Constructor for a Task. + * Should not be used directly; construct one of the child classes instead + * + * @param input User input + */ + public Task(String input) { + this.input = input; + } + + public void markAsDone() { + this.isComplete = true; + } + + public void unmarkAsDone() { + this.isComplete = false; + } + + public String getType() { + return this.type; + } + + /** + * Gets the symbol for whether this task is complete or not + * + * @return A string symbol + */ + protected String getDoneSymbol() { + if (this.isComplete) { + return "[X]"; + } else { + return "[ ]"; + } + } + + + /** + * Returns description of task + * + * @return Description string + */ + protected String getDescription() { + return this.description; + } + + /** + * Gets the symbol for the type of this task + * + * @return A string symbol + */ + protected String getTaskTypeSymbol() { + if (this.type.equals("todo")) { + return "[T]"; + } else if (this.type.equals("deadline")) { + return "[D]"; + } else if (this.type.equals("event")) { + return "[E]"; + } else return null; + } + + @Override + public String toString() { + String doneSymbol = this.getDoneSymbol(); + String taskTypeSymbol = this.getTaskTypeSymbol(); + String description = this.getDescription(); + + String output = taskTypeSymbol + doneSymbol + " " + description; + + return output; + } + +} diff --git a/src/main/java/duke/TaskList.java b/src/main/java/duke/TaskList.java new file mode 100644 index 0000000000..0153f7b755 --- /dev/null +++ b/src/main/java/duke/TaskList.java @@ -0,0 +1,9 @@ +package duke; + +import java.util.ArrayList; + +public class TaskList { + + public static ArrayList taskList = new ArrayList<>(); + +} \ No newline at end of file diff --git a/src/main/java/duke/TodoTask.java b/src/main/java/duke/TodoTask.java new file mode 100644 index 0000000000..25365bf87d --- /dev/null +++ b/src/main/java/duke/TodoTask.java @@ -0,0 +1,9 @@ +package duke; + +public class TodoTask extends Task { + public TodoTask(String input) { + super(input); + this.type = "todo"; + this.description = this.input; // Updates description of class + } +} diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/Ui.java new file mode 100644 index 0000000000..0c7f259aef --- /dev/null +++ b/src/main/java/duke/Ui.java @@ -0,0 +1,114 @@ +package duke; + +import java.util.ArrayList; +import java.util.Scanner; + +public class Ui { + private Scanner sc; + + public Ui() { + this.sc = new Scanner(System.in); + System.setOut(DukeGUI.outputStream); + } + + + /** + * Waits for user input and returns it + * + * @return Next line of input from user + */ + public String getNextLine() { + return sc.nextLine(); + } + + /** + * Duke's standard output line + * + * @return String of a line + */ + private static String line() { + String line = "____________________________________________________________"; + return line; + } + + /** + * Formatted output for confirmation of a new added task + * + * @param task Newly added task + */ + public static void printAddTask(Task task) { + System.out.println("Added:"); + System.out.println(task.toString()); + } + + /** + * Formatted output for confirmation of program start + */ + public static void printIntro() { + String INTRO = "Duke initialised"; + System.out.println(INTRO); + } + + /** + * Formatted output for confirmation of program exit + */ + public static void printExit() { + String EXIT = "Duke terminated"; + System.out.println(EXIT); + } + + /** + * Formatted output for printing the task list to the user + * + * @param formattedTaskList Formatted task list from the task list array + */ + public static void printTaskList(String formattedTaskList) { + System.out.println(formattedTaskList); + } + + /** + * Formatted output for confirmation of marking a task + * + * @param task Task + */ + public static void printMarkTask(Task task) { + System.out.println("Task marked as done:"); + System.out.println(task.toString()); + } + + /** + * Formatted output for confirmation of deleting a task + * + * @param task Task + */ + public static void printDeleteTask(Task task) { + System.out.println("Task deleted:"); + System.out.println(task.toString()); + } + + /** + * Formatted output for confirmation of unmarking a task + * + * @param task Task + */ + public static void printUnmarkTask(Task task) { + System.out.println("Task marked as not done:"); + System.out.println(task.toString()); + } + + /** + * Formatted output for tasks found by a search term + * + * @param taskList List of found tasks + */ + + public static void printFindTask(ArrayList taskList) { + String FOUND = "Matching tasks:"; + System.out.println(FOUND); + + for (Task t : taskList) { + System.out.println(t.toString()); + } + + } +} diff --git a/src/main/java/duke/UnmarkCommand.java b/src/main/java/duke/UnmarkCommand.java new file mode 100644 index 0000000000..6fd617722d --- /dev/null +++ b/src/main/java/duke/UnmarkCommand.java @@ -0,0 +1,26 @@ +package duke; + +import java.util.ArrayList; + +/** + * Marks the indicated task as undone + */ +public class UnmarkCommand extends Command { + private String input; // Parameters: 1 + public ArrayList taskList = TaskList.taskList; + private int index; // Index of target task + + public UnmarkCommand(String i) { + this.input = i; + index = Integer.parseInt(input) - 1; // -1 because list starts at 1 while indexes start at 0 + } + + @Override + public void execute() { + int index = Integer.parseInt(input) - 1; // -1 because list starts at 1 while indexes start at 0 + Task task = TaskList.taskList.get(index); + task.unmarkAsDone(); + + Ui.printUnmarkTask(task); + } +} \ No newline at end of file diff --git a/src/main/resources/DaDuke.png b/src/main/resources/DaDuke.png new file mode 100644 index 0000000000..d893658717 Binary files /dev/null and b/src/main/resources/DaDuke.png differ diff --git a/src/main/resources/DaUser.png b/src/main/resources/DaUser.png new file mode 100644 index 0000000000..3c82f45461 Binary files /dev/null and b/src/main/resources/DaUser.png differ diff --git a/src/test/java/duke/ParserTest.java b/src/test/java/duke/ParserTest.java new file mode 100644 index 0000000000..7819e59d0b --- /dev/null +++ b/src/test/java/duke/ParserTest.java @@ -0,0 +1,24 @@ +package duke; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ParserTest { + + @Test + public void makeCommand_ByeAsInput_ReturnByeCommand() { + Parser p = new Parser(); + Command c = p.makeCommand("bye"); + boolean result = c instanceof ByeCommand; + assertEquals(result, true); + } + + @Test + public void makeCommand_ListAsInput_ReturnListCommand() { + Parser p = new Parser(); + Command c = p.makeCommand("list"); + boolean result = c instanceof ListCommand; + assertEquals(result, true); + } +} diff --git a/src/test/java/duke/TaskTest.java b/src/test/java/duke/TaskTest.java new file mode 100644 index 0000000000..7ff104c128 --- /dev/null +++ b/src/test/java/duke/TaskTest.java @@ -0,0 +1,23 @@ +package duke; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TaskTest { + + @Test + public void markAsDone_CallOnTask_ChangesIsCompleteToTrue() { + Task t = new Task("INPUT"); + t.markAsDone(); + assertEquals(t.isComplete, true); + } + + @Test + public void unmarkAsDone_CallOnTask_ChangesIsCompleteToFalse() { + Task t = new Task("INPUT"); + t.unmarkAsDone(); + assertEquals(t.isComplete, false); + } +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e7..4887bc0f4f 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,52 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| + ____________________________________________________________ + Duke initialised + ____________________________________________________________ + + ____________________________________________________________ + added: [E][ ] zoo visit with friends (at: 4-6pm tomorrow) + ____________________________________________________________ + + ____________________________________________________________ + added: [T][ ] pay electricity bills + ____________________________________________________________ + + ____________________________________________________________ + added: [D][ ] school project (by: 10pm next week monday) + ____________________________________________________________ + + ____________________________________________________________ + 1. [E][ ] zoo visit with friends (at: 4-6pm tomorrow) + 2. [T][ ] pay electricity bills + 3. [D][ ] school project (by: 10pm next week monday) + ____________________________________________________________ + + ____________________________________________________________ + Task(s) marked as done: + [E][X] zoo visit with friends (at: 4-6pm tomorrow) + ____________________________________________________________ + + ____________________________________________________________ + Task(s) marked as done: + [D][X] school project (by: 10pm next week monday) + ____________________________________________________________ + + ____________________________________________________________ + Task(s) unmarked as done: + [D][ ] school project (by: 10pm next week monday) + ____________________________________________________________ + + ____________________________________________________________ + Task(s) marked as done: + [T][X] pay electricity bills + ____________________________________________________________ + + ____________________________________________________________ + 1. [E][X] zoo visit with friends (at: 4-6pm tomorrow) + 2. [T][X] pay electricity bills + 3. [D][ ] school project (by: 10pm next week monday) + ____________________________________________________________ + + ____________________________________________________________ + Duke terminated + ____________________________________________________________ diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb2..d5bc20c457 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,10 @@ +event zoo visit with friends /at 4-6pm tomorrow +todo pay electricity bills +deadline school project /by 10pm next week monday +list +mark 1 +mark 3 +unmark 3 +mark 2 +list +bye \ No newline at end of file diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 0873744649..0c79b0b7c6 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -19,3 +19,5 @@ java -classpath ..\bin Duke < input.txt > ACTUAL.TXT REM compare the output to the expected output FC ACTUAL.TXT EXPECTED.TXT + +pause \ No newline at end of file diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh old mode 100644 new mode 100755