diff --git a/README.md b/README.md index 8715d4d9..938cdbe7 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,32 @@ -# 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).
- 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 - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - ``` +# Duke assignment TIC2002 + +Program that takes in user input and create a list of task which should benefit the user. + +## Commands +Add and assignment to do. +`Todo` --- **todo assignment** + +Add an assignment with deadline on 10 Oct 2021 2359 +`Deadline` --- **deadline assignment submission /by 2021-10-10 2359** + +Add an event that happens from 21 Dec 2021 8PM to 9:30PM +`Event` --- **event live concert /at 2021-12-21 2000 /for 1:30** + +List all task. +`List` --- **list** + +Set a task as done according to the order in list. Sets task 1 as done if it exists. +`Done` --- **done 1** + +Delete a task according to the order in list. Deletes task 1 if it exists. +`Delete` --- **delete 1** + +Find the task with description matching the keyword. Find all task with assignment in description. +`Find` --- **find assignment** + +View the task with date matching the search date. Find all task which occurs on Nov 15 2021. +`View` --- **view 2021-11-15** + +Saves and exit program. +`Bye` --- **bye** + diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..977f5f8c --- /dev/null +++ b/build.gradle @@ -0,0 +1,70 @@ +plugins { + id 'java' + id 'application' + id 'checkstyle' + id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'org.openjfx.javafxplugin' version '0.0.10' +} + + + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.jetbrains:annotations:20.1.0' + 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' + + String javaFxVersion = '11' + + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + +application { + mainClassName = "Launcher" +} + +shadowJar { + archiveBaseName = "duke" + archiveClassifier = null +} + +checkstyle { + toolVersion = '8.29' +} + +run{ + standardInput = System.in +} + +javafx { + version = "17" + modules = [ 'javafx.controls', 'javafx.fxml' ] +} diff --git a/data/tasks.txt b/data/tasks.txt new file mode 100644 index 00000000..696d7000 --- /dev/null +++ b/data/tasks.txt @@ -0,0 +1,5 @@ +D | 1 | Complete assignment | 2021-10-21 2359 +E | 0 | Concert | 2021-10-22 1300 | 01:30 +D | 0 | Assignment 2 | 2021-11-30 0001 +T | 0 | Sumbit assignment +E | 0 | concert 2 | 2021-11-21 2000 | 02:30 diff --git a/data/test.txt b/data/test.txt new file mode 100644 index 00000000..37521afe --- /dev/null +++ b/data/test.txt @@ -0,0 +1,3 @@ +D | 1 | Complete assignment | 2021-10-21 2359 +E | 0 | Concert | 2021-10-22 1300 +D | 0 | Assignment 2 | 2021-11-30 0001 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..f3d88b1c 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 00000000..b7c8c5db --- /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 00000000..2fe81a7d --- /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 00000000..62bd9b9c --- /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/DialogBox.java b/src/main/java/DialogBox.java new file mode 100644 index 00000000..16e5c0d2 --- /dev/null +++ b/src/main/java/DialogBox.java @@ -0,0 +1,62 @@ +import java.io.IOException; +import java.util.Collections; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; + + +/** + * An example of a custom control using FXML. + * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label + * containing text from the speaker. + */ +public class DialogBox extends HBox { + @FXML + private Label dialog; + @FXML + private ImageView displayPicture; + + private DialogBox(String text, Image img) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + dialog.setText(text); + displayPicture.setImage(img); + + + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + Collections.reverse(tmp); + getChildren().setAll(tmp); + setAlignment(Pos.TOP_LEFT); + } + + public static DialogBox getUserDialog(String text, Image img) { + return new DialogBox(text, img); + } + + public static DialogBox getDukeDialog(String text, Image img) { + var db = new DialogBox(text, img); + db.flip(); + return db; + } +} \ No newline at end of file diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 5d313334..9cf7c85d 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,10 +1,114 @@ +import parser.Parser; +import storage.Storage; +import ui.UI; +import task.List; +import command.Command; + +import error.DukeException; +import error.FileException; +import error.UnrecognizedException; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + + public class Duke { + + private UI ui; + private Parser Parser; + private List tasks; + private Storage storage; + private boolean isExit = false; + private static String Path = "data/tasks.txt"; + ; + + /** + * Constructor for new Duke object using filePath given in main. + * Creates a new ui.UI, parser.Parser and storage.Storage object. + * Try to load a new List object from path. + * If path does not exist, create a new empty List object + * + * @param filePath Path of file to be executed + */ + + public Duke(String filePath) { + ui = new UI(); + Parser = new Parser(); + storage = new Storage(filePath); + try { + tasks = new List(storage.load()); + System.out.println("File loaded successfully."); + } catch (FileException e) { + System.out.println("File format is corrupted. Creating new file."); + tasks = new List(); + } catch (Exception e) { + System.out.println("Error reading file or does not exist. Creating new file."); + tasks = new List(); + } + } + + + //javaFX + public String getResponse(String input) { + assert input.length() > 0; + ByteArrayOutputStream outputMsg = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputMsg)); + try { + Command c = Parser.parse(input); + c.execute(tasks, storage, ui); + if (c.isExit()) { + isExit = true; + } + return outputMsg.toString(); + + } catch (UnrecognizedException e) { + return ("Unrecognized Command"); + } catch (ArrayIndexOutOfBoundsException e) { + return ("Please enter description after command"); + } catch (DukeException e) { + ui.showError(e.getMessage()); + return outputMsg.toString(); + } + } + + public Boolean getIsExit() { + return isExit; + } + + /* Unused after JavaFX implementation public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); + new Duke(Path).run(); + } + */ + /** + * + * Executes the program until isExit is true + * + */ + /* Unused after JavaFX implementation + public void run(){ + ui.printIntro(); + + while (!isExit){ + try{ + String fullCommand = ui.readCommand(); + ui.printLine(); + assert fullCommand.length() > 0; + Command c = Parser.parse(fullCommand); + c.execute(tasks,storage,ui); + isExit = c.isExit(); + } catch (UnrecognizedException e){ + System.out.println("Unrecognized Command"); + } catch (ArrayIndexOutOfBoundsException e) { + System.out.println("Please enter description after command"); + } catch (DukeException e){ + ui.showError(e.getMessage()); + } + finally { + ui.printLine(); + } + } + } + */ } diff --git a/src/main/java/Launcher.java b/src/main/java/Launcher.java new file mode 100644 index 00000000..11dbf00c --- /dev/null +++ b/src/main/java/Launcher.java @@ -0,0 +1,10 @@ +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 00000000..9475278e --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,33 @@ +import java.io.IOException; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + +/** + * A GUI for Duke using FXML. + */ +public class Main extends Application { + private static String Path = "data/tasks.txt"; + ; + + private Duke duke = new Duke(Path); + + @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 00000000..3b8c5cf8 --- /dev/null +++ b/src/main/java/MainWindow.java @@ -0,0 +1,62 @@ +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 java.util.concurrent.TimeUnit; + +/** + * 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/DaUser.png")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png")); + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + String intro = "Hello. Welcome to Toh Shao Wei TIC2002 Project. Please enter your instruction." + + "\nEnter help to see the list of instructions."; + dialogContainer.getChildren().addAll( + DialogBox.getDukeDialog(intro, 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. + */ + @FXML + private void handleUserInput() { + String input = userInput.getText(); + String response = duke.getResponse(input); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(input, userImage), + DialogBox.getDukeDialog(response, dukeImage) + ); + userInput.clear(); + + if (duke.getIsExit()) { + Platform.exit(); + } + } + +} \ No newline at end of file diff --git a/src/main/java/command/AddCommand.java b/src/main/java/command/AddCommand.java new file mode 100644 index 00000000..9cdff3f5 --- /dev/null +++ b/src/main/java/command/AddCommand.java @@ -0,0 +1,54 @@ +package command; + +import storage.Storage; +import ui.UI; +import task.List; +import error.*; + +public class AddCommand extends Command { + protected String add; + + public AddCommand(CommandType action, String add) { + setAction(action); + setAdd(add); + } + + public void setAdd(String add) { + this.add = add; + } + + public void execute(List tasks, Storage storage, UI ui) { + try { + addTask(action, add, tasks); + } catch (DukeException e) { + ui.showError(e.getMessage()); + } + + } + /** + * task function to determine the type of task to add. + * Action is an enumeration of TODO, DEADLINE and EVENT, + * where the String action only allows todo, deadline and event. + * Converts action into an Enumeration and determine the type of task to + * be added. inputMsg determines what is the full command description sent + * when adding task. + * + * @param action the type of task to be added + * @param inputMsg the message be added in taskArrayList + */ + public void addTask(CommandType action, String inputMsg, List tasks) throws DukeException { + switch (action) { + case TODO: + tasks.addTodo(inputMsg); + break; + case DEADLINE: + tasks.addDeadline(inputMsg); + break; + case EVENT: + tasks.addEvent(inputMsg); + break; + default: + assert false : action; + } + } +} diff --git a/src/main/java/command/Command.java b/src/main/java/command/Command.java new file mode 100644 index 00000000..970b2ed5 --- /dev/null +++ b/src/main/java/command/Command.java @@ -0,0 +1,25 @@ +package command; + +import storage.Storage; +import ui.UI; +import task.List; + +public abstract class Command { + protected CommandType action; + + public Command() { + + } + + public void setAction(CommandType action) { + this.action = action; + } + + public boolean isExit() { + return false; + } + + public abstract void execute(List tasks, Storage storage, UI ui); + + +} diff --git a/src/main/java/command/CommandType.java b/src/main/java/command/CommandType.java new file mode 100644 index 00000000..97be801a --- /dev/null +++ b/src/main/java/command/CommandType.java @@ -0,0 +1,38 @@ +package command; + +import error.DukeException; +import error.UnrecognizedException; + +public enum CommandType { + + TODO("todo"), + DEADLINE("deadline"), + EVENT("event"), + BYE("bye"), + DELETE("delete"), + DONE("done"), + LIST("list"), + FIND("find"), + VIEW("view"), + HELP("help"); + + private String commandType; + + CommandType(String commandType) { + this.commandType = commandType; + } + + public String getCommandType() { + return commandType; + } + + public static CommandType getCommandType(String inputAction) throws DukeException { + for (CommandType command : CommandType.values()) { + if (command.getCommandType().equals(inputAction)) { + return command; + } + } + assert false; + throw new DukeException("INVALID_ACTION"); + } +} diff --git a/src/main/java/command/ExitCommand.java b/src/main/java/command/ExitCommand.java new file mode 100644 index 00000000..e5b4440e --- /dev/null +++ b/src/main/java/command/ExitCommand.java @@ -0,0 +1,22 @@ +package command; + +import storage.Storage; +import ui.UI; +import task.List; + +public class ExitCommand extends Command { + public ExitCommand() { + + } + + public void execute(List tasks, Storage storage, UI ui) { + tasks.saveList(); + ui.printExit(); + storage.saveFile(tasks.getSave()); + } + + @Override + public boolean isExit() { + return true; + } +} diff --git a/src/main/java/command/HelpCommand.java b/src/main/java/command/HelpCommand.java new file mode 100644 index 00000000..d2027948 --- /dev/null +++ b/src/main/java/command/HelpCommand.java @@ -0,0 +1,15 @@ +package command; + +import storage.Storage; +import task.List; +import ui.UI; + +public class HelpCommand extends Command { + public HelpCommand() { + + } + + public void execute(List tasks, Storage storage, UI ui) { + ui.printInstruction(); + } +} \ No newline at end of file diff --git a/src/main/java/command/ListCommand.java b/src/main/java/command/ListCommand.java new file mode 100644 index 00000000..48152b0f --- /dev/null +++ b/src/main/java/command/ListCommand.java @@ -0,0 +1,15 @@ +package command; + +import storage.Storage; +import ui.UI; +import task.List; + +public class ListCommand extends Command { + public ListCommand() { + + } + + public void execute(List tasks, Storage storage, UI ui) { + tasks.printList(); + } +} \ No newline at end of file diff --git a/src/main/java/command/ModifyCommand.java b/src/main/java/command/ModifyCommand.java new file mode 100644 index 00000000..0e455d4f --- /dev/null +++ b/src/main/java/command/ModifyCommand.java @@ -0,0 +1,39 @@ +package command; + +import storage.Storage; +import ui.UI; +import task.List; +import error.*; + +public class ModifyCommand extends Command { + protected String modify; + + public ModifyCommand(CommandType action, String modify) { + setAction(action); + setModify(modify); + } + + public void setModify(String modify) { + this.modify = modify; + } + + public void execute(List tasks, Storage storage, UI ui) { + try { + switch (action) { + case DONE: + tasks.taskDone(modify); + break; + case DELETE: + tasks.taskDelete(modify); + break; + default: + assert false : action; + } + + } catch (NotFoundException e) { + ui.printNotFound(); + } catch (NumberFormatException e) { + ui.printInvalidEntry(); + } + } +} \ No newline at end of file diff --git a/src/main/java/command/SearchCommand.java b/src/main/java/command/SearchCommand.java new file mode 100644 index 00000000..3b9aa025 --- /dev/null +++ b/src/main/java/command/SearchCommand.java @@ -0,0 +1,33 @@ +package command; + +import error.DukeException; +import storage.Storage; +import ui.UI; +import task.List; + +public class SearchCommand extends Command { + protected String searchValue; + + public SearchCommand(CommandType action, String searchValue) { + setAction(action); + setSearchValue(searchValue); + } + + public void setSearchValue(String searchValue) { + this.searchValue = searchValue; + } + + public void execute(List tasks, Storage storage, UI ui) { + try { + switch (action) { + case FIND: + tasks.printSearchList((searchValue)); + break; + case VIEW: + tasks.printSchedule(searchValue); + } + } catch (DukeException e) { + ui.showError(e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/error/DukeException.java b/src/main/java/error/DukeException.java new file mode 100644 index 00000000..8995fcf2 --- /dev/null +++ b/src/main/java/error/DukeException.java @@ -0,0 +1,8 @@ +package error; + +public class DukeException extends Exception { + public DukeException (String errorMessage){ + super(errorMessage); + } + +} diff --git a/src/main/java/error/FileException.java b/src/main/java/error/FileException.java new file mode 100644 index 00000000..6d0c7837 --- /dev/null +++ b/src/main/java/error/FileException.java @@ -0,0 +1,4 @@ +package error; + +public class FileException extends Exception{ +} diff --git a/src/main/java/error/NotFoundException.java b/src/main/java/error/NotFoundException.java new file mode 100644 index 00000000..47caec63 --- /dev/null +++ b/src/main/java/error/NotFoundException.java @@ -0,0 +1,4 @@ +package error; + +public class NotFoundException extends Exception{ +} diff --git a/src/main/java/error/UnrecognizedException.java b/src/main/java/error/UnrecognizedException.java new file mode 100644 index 00000000..23436f5c --- /dev/null +++ b/src/main/java/error/UnrecognizedException.java @@ -0,0 +1,6 @@ +package error; + +public class UnrecognizedException extends Exception{ + + +} diff --git a/src/main/java/parser/Parser.java b/src/main/java/parser/Parser.java new file mode 100644 index 00000000..7377ba61 --- /dev/null +++ b/src/main/java/parser/Parser.java @@ -0,0 +1,55 @@ +package parser; + +import command.CommandType; +import command.Command; +import command.AddCommand; +import command.ExitCommand; +import command.HelpCommand; +import command.ListCommand; +import command.ModifyCommand; +import command.SearchCommand; +import error.DukeException; +import error.UnrecognizedException; + + +public class Parser { + /** + * Parser to read user input + * + * @param input users input message + * @return type of command + * @throws UnrecognizedException if action is not a recognized command + * @throws DukeException if format has a "|" which will conflict with save format + */ + public Command parse(String input) throws UnrecognizedException, DukeException { + if (input.contains("|")) { + throw new DukeException("INPUT_FORMAT_ERROR"); + } + String action; + String[] inputArray; + inputArray = input.split(" ", 2); + action = inputArray[0].toLowerCase(); + CommandType commandType = CommandType.getCommandType(action); + switch (commandType) { + case BYE: + return new ExitCommand(); + case TODO: + case DEADLINE: + case EVENT: + return new AddCommand(commandType, inputArray[1]); + case DELETE: + case DONE: + return new ModifyCommand(commandType, inputArray[1]); + case LIST: + return new ListCommand(); + case FIND: + case VIEW: + return new SearchCommand(commandType, inputArray[1]); + case HELP: + return new HelpCommand(); + default: + assert false:commandType; + throw new UnrecognizedException(); + } + } +} diff --git a/src/main/java/storage/Storage.java b/src/main/java/storage/Storage.java new file mode 100644 index 00000000..1db79110 --- /dev/null +++ b/src/main/java/storage/Storage.java @@ -0,0 +1,83 @@ +package storage; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Scanner; +import java.io.FileWriter; +import java.io.IOException; + +public class Storage { + private static String filePath; + private ArrayList loadFile; + public Storage(String filePath){ + this.filePath = filePath; + loadFile = new ArrayList<>(); + } + + /** + * + * Creates a new file with parent directory if it does not exist + * + */ + public void newFile(){ + try { + File f = new File(filePath); + f.getParentFile().mkdirs(); + if (f.createNewFile()) + System.out.println("File created"); + else + System.out.println("Loading existing file"); + } catch (Exception e) { + System.err.println(e); + } + } + + /** + * + * Creates a new file with parent directory if it does not exist + * + * @param saveFileList an array of strings to save into the txt file in path. + */ + public void saveFile(ArrayList saveFileList){ + try { + writeToFile(saveFileList); + } catch (IOException e) { + System.out.println("Something went wrong: " + e.getMessage()); + } + } + + /** + * + * Create a new + * + * @return loaded file + */ + public ArrayList load(){ + try { + readFile(filePath); + } catch (FileNotFoundException e) { + newFile(); //create new file + System.out.println("File not found"); + } + return loadFile; + } + + private void writeToFile(ArrayList saveFileList) throws IOException { + FileWriter fw = new FileWriter(filePath); + for(String saveFile : saveFileList) { + fw.write(saveFile + System.lineSeparator()); + } + fw.close(); + } + + private void readFile(String filePath) throws FileNotFoundException { + File f = new File(filePath); + Scanner s = new Scanner(f); + while (s.hasNext()) { + String read = s.nextLine(); + loadFile.add(read); + } + } + +} diff --git a/src/main/java/task/Action.java b/src/main/java/task/Action.java new file mode 100644 index 00000000..44d39813 --- /dev/null +++ b/src/main/java/task/Action.java @@ -0,0 +1,22 @@ +package task; + +public enum Action { + TODO("todo"), + DEADLINE("deadline"), + EVENT("event"); + + private String addType; + + Action(String addType) { + setAddType(addType); + } + + public void setAddType(String addType) { + this.addType = addType; + } + + public String getAddType() { + return addType; + } + +} diff --git a/src/main/java/task/Deadline.java b/src/main/java/task/Deadline.java new file mode 100644 index 00000000..0473e263 --- /dev/null +++ b/src/main/java/task/Deadline.java @@ -0,0 +1,102 @@ +package task; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + + +public class Deadline extends Task { + private final String PRINT_FORMAT = "MMM d yyyy HH:mm a"; + private final String SAVE_FORMAT = "yyyy-MM-dd HHmm"; + private final String DATE_FORMAT = "yyyy-MM-dd"; + protected LocalDateTime by; + + /** + * Constructor for a new deadline created by user command + * sets the description and by of the task to be description and by argument respectively + * and isDone is always false for newly added task. + * + * @param description description of the task + * @param by date of task to be completed by + */ + public Deadline(String description, LocalDateTime by) { + super(description); + setBy(by); + setType(); + } + + /** + * Constructor for a new task created by loading file + * sets the description and by of the task to be description and by argument respectively. + * isDone argument determines whether task isDone is true or false. + * + * @param description description of the task + * @param by date of task to be completed by + * @param isDone is program set as done + */ + public Deadline(String description, LocalDateTime by, Boolean isDone) { + super(description); + setBy(by); + setType(); + this.isDone = isDone; + } + + public void setDone() { + this.isDone = true; + System.out.println("Nice! I've marked this task as done:\n" + + " [D][X] " + getDescription() + "(by: " + getByFormat() + ")"); + } + + public void setType() { + type = Action.DEADLINE; + } + + public void print() { + if (isDone) { + System.out.println(" [D][X] " + getDescription() + "(by: " + getByFormat() + ")"); + } else { + System.out.println(" [D][ ] " + getDescription() + "(by: " + getByFormat() + ")"); + } + + } + + public void setBy(LocalDateTime by) { + this.by = by; + } + + public String getTask() { + return "D"; + } + + public String getDescription() { + return description; + } + + public String getByFormat() { + return by.format(DateTimeFormatter.ofPattern(PRINT_FORMAT)); + } + + public LocalDateTime getDateTime() { + return by; + } + + /** + * Returns the format of the task to be saved. + * gets task, isDone and description separated by ' | ' + * + * @return the file format + */ + public String getSave() { + String s = getTask() + " | " + getDone() + " | " + getDescription() + " | " + + getDateTime().format(DateTimeFormatter.ofPattern(SAVE_FORMAT)); + return s; + } + + @Override + public String toString() { + String box = "[D][ ] "; + if (isDone) { + box = "[D][X] "; + } + return (box + description + "(by: " + getByFormat() + ")"); + } +} \ No newline at end of file diff --git a/src/main/java/task/Event.java b/src/main/java/task/Event.java new file mode 100644 index 00000000..b25cd9da --- /dev/null +++ b/src/main/java/task/Event.java @@ -0,0 +1,128 @@ +package task; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.LocalTime; + +public class Event extends Task { + protected LocalDateTime eventTime; + protected LocalTime to; + + private final String PRINT_FORMAT = "MMM d yyyy HH:mm a"; + private final String DATE_FORMAT = "yyyy-MM-dd"; + private final String SAVE_FORMAT = "yyyy-MM-dd HHmm"; + + /** + * Constructor for a new event created by user command + * sets the description, eventTime and to of the task to be + * description, eventTime and to argument respectively + * and isDone is always false for newly added task. + * + * @param description description of the task + * @param eventTime start time of event + * @param to how long the event will be + */ + public Event(String description, LocalDateTime eventTime, LocalTime to) { + super(description); + setEventTime(eventTime); + setTo(to); + setType(); + } + + /** + * Constructor for a new task created by loading file + * sets the description, eventTime and to of the task to be + * description, eventTime and to argument respectively + * isDone argument determines whether task isDone is true or false. + * + * @param description description of the task + * @param eventTime start time of event + * @param to how long the event will be + * @param isDone is program set as done + */ + public Event(String description, LocalDateTime eventTime, LocalTime to, Boolean isDone) { + super(description); + setEventTime(eventTime); + setTo(to); + setType(); + this.isDone = isDone; + } + + public void setDone() { + this.isDone = true; + System.out.println("Nice! I've marked this task as done:\n" + + " [E][X] " + getDescription() + "(at: " + getEventTimeFormat() + ")"); + } + + public void print() { + if (isDone) { + System.out.println(" [E][X] " + getDescription() + "(at: " + getEventTimeFormat() + ")"); + } else { + System.out.println(" [E][ ] " + getDescription() + "(at: " + getEventTimeFormat() + ")"); + } + + } + + public void setEventTime(LocalDateTime eventTime) { + this.eventTime = eventTime; + } + + public void setTo(LocalTime to) { + this.to = to; + } + + public void setType() { + type = Action.EVENT; + } + + public String getTask() { + return "E"; + } + + public String getDescription() { + return description; + } + + public String getEventTimeFormat() { + return eventTime.format(DateTimeFormatter.ofPattern(PRINT_FORMAT)); + } + + public LocalDateTime getDateTime() { + return eventTime; + } + + public LocalTime getTime() { + return to; + } + + public String getHours() { + String hours = to.getHour() + " Hours "; + return hours; + } + + public String getMinutes() { + String minutes = to.getMinute() + " Minutes "; + return minutes; + } + + /** + * Returns the format of the task to be saved. + * gets task, isDone and description separated by ' | ' + * + * @return the file format + */ + public String getSave() { + String s = getTask() + " | " + getDone() + " | " + getDescription() + " | " + + getDateTime().format(DateTimeFormatter.ofPattern(SAVE_FORMAT)) + " | " + getTime(); + return s; + } + + @Override + public String toString() { + String box = "[E][ ] "; + if (isDone) { + box = "[E][X] "; + } + return (box + getDescription() + "(at: " + getEventTimeFormat() + " for " + getHours() + getMinutes() + ")"); + } +} diff --git a/src/main/java/task/EventDateTime.java b/src/main/java/task/EventDateTime.java new file mode 100644 index 00000000..9c3dda04 --- /dev/null +++ b/src/main/java/task/EventDateTime.java @@ -0,0 +1,52 @@ +package task; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +public class EventDateTime { + public LocalDateTime startTime; + public LocalDateTime endTime; + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); + + public EventDateTime(LocalDateTime dateTime, LocalTime time) { + setEventDate(dateTime, time); + } + + public void setEventDate(LocalDateTime dateTime, LocalTime time) { + startTime = dateTime; + int hours = time.getHour(); + int minutes = time.getMinute(); + endTime = startTime.plusHours(hours).plusMinutes(minutes); + } + + public LocalDateTime getStartTime() { + return startTime; + } + + public LocalDateTime getEndTime() { + return endTime; + } + + /** + * Compares the event to be added with existing event to find any anomaly + * where startTime of event to be added is between the existing event start and end time + * Or endTime of event to be added is between the existing event start and end time. + * + * @param exist Date and time of existing task + * @return true if anomaly detected else returns false + */ + public Boolean isAnomaly(EventDateTime exist) { + if (getStartTime().isAfter(exist.getStartTime()) && getStartTime().isBefore(exist.getEndTime())) { + return true; + } else if (getEndTime().isAfter(exist.getStartTime()) && getEndTime().isBefore(exist.getEndTime())) { + return true; + } else if (getStartTime().isBefore(exist.getStartTime()) && getEndTime().isAfter(exist.getStartTime())) { + return true; + } else if (getStartTime().isEqual(exist.getStartTime()) || getEndTime().isEqual(exist.getEndTime())) { + return true; + } + return false; + } + +} diff --git a/src/main/java/task/List.java b/src/main/java/task/List.java new file mode 100644 index 00000000..31707394 --- /dev/null +++ b/src/main/java/task/List.java @@ -0,0 +1,320 @@ +package task; + +import error.DukeException; +import error.FileException; +import error.NotFoundException; + +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.time.LocalDateTime; +import java.time.LocalDate; +import java.time.LocalTime; + + +public class List { + private static ArrayList taskArrayList; + private static ArrayList taskSave; + public static Task recentDelete; + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern( + "yyyy-MM-dd HHmm"); + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern( + "yyyy-MM-dd"); + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("H:mm"); + + /** + * Create new a new empty List + */ + public List() { + taskArrayList = new ArrayList<>(); + taskSave = new ArrayList<>(); + } + + /** + * Create new a new loaded List + * + * @param loadFile loaded txt file + */ + public List(ArrayList loadFile) throws FileException { + taskArrayList = new ArrayList<>(); + taskSave = new ArrayList<>(); + String[] taskArr; + loadFile.listIterator(); + for (String task : loadFile) { + taskArr = task.split(" \\| "); + String taskType, taskDone, taskDescription; + LocalDateTime date; + LocalTime time; + Boolean isDone = false; + taskType = taskArr[0]; + taskDone = taskArr[1]; + taskDescription = taskArr[2]; + if (taskDone.equals("1")) { + isDone = true; + } + switch (taskType) { + case "T": + taskArrayList.add(new Todo(taskDescription, isDone)); + break; + case "D": + try { + date = LocalDateTime.parse(taskArr[3], DATE_TIME_FORMATTER); + taskArrayList.add(new Deadline(taskDescription, date, isDone)); + } catch (Exception e) { + throw new FileException(); + } + break; + + case "E": + try { + date = LocalDateTime.parse(taskArr[3], DATE_TIME_FORMATTER); + time = LocalTime.parse(taskArr[4], TIME_FORMATTER); + taskArrayList.add(new Event(taskDescription, date, time, isDone)); + } catch (Exception e) { + throw new FileException(); + } + break; + + } + } + + } + + /** + * Adds new to do task to taskList as a TODO object + * The inputMsg contains the instruction all input is part of the description + * "description" + * On successful adding of task, it prints the task added + * + * @param inputMsg description to be added + * @throws DukeException if inputMsg is empty + */ + public void addTodo(String inputMsg) throws DukeException { + if (inputMsg.isEmpty()) { + throw new DukeException("TODO_DESCRIPTION_ERROR"); + } + taskArrayList.add(new Todo(inputMsg)); + printAdd(); + } + + /** + * Adds new deadline task to taskList as a DEADLINE object. + * The inputMsg contains the instruction where only the format + * "description" /by "DateTime" is accepted + * On successful adding of task, it prints the task added. + * + * @param inputMsg description and /by to be added + * @throws DukeException if inputMsg is does not contain /by or more than 1 /at OR date is not valid + */ + public void addDeadline(String inputMsg) throws DukeException { + String[] input = inputMsg.split(" /by "); + LocalDateTime date; + if (input.length < 2) { + throw new DukeException("DEADLINE_DESCRIPTION_ERROR"); + } else if (input.length > 2) { + throw new DukeException("DEADLINE_LENGTH_ERROR"); + } + try { + date = LocalDateTime.parse(input[1], DATE_TIME_FORMATTER); + } catch (Exception e) { + throw new DukeException("INVALID_DATETIME_FORMAT"); + } + taskArrayList.add(new Deadline(input[0], date)); + printAdd(); + } + + /** + * Adds new event task to taskList as a EVENT object + * The inputMsg contains the instruction where only the format + * "description" /by "DateTime" /for "Time" is accepted + * It checks if there is any existing task with clashing timings and + * on successful adding of task, it prints the task added. + * + * @param inputMsg description and /at to be added + * @throws DukeException if inputMsg does not contain /at or more than 1 /at + */ + public void addEvent(String inputMsg) throws DukeException { + String[] input = inputMsg.split(" /at "); + String[] event; + LocalDateTime date; + LocalTime time; + Boolean clash = false; + assert input.length == 2; + if (input.length < 2) { + throw new DukeException("EVENT_DESCRIPTION_ERROR"); + } + if (input.length > 2) { + throw new DukeException("EVENT_LENGTH_ERROR"); + } + event = input[1].split(" /for "); + if (event.length > 2) { + throw new DukeException("EVENT_LENGTH_ERROR"); + } + try { + date = LocalDateTime.parse(event[0], DATE_TIME_FORMATTER); + } catch (Exception e) { + throw new DukeException("INVALID_DATETIME_FORMAT"); + } + try { + time = LocalTime.parse(event[1], TIME_FORMATTER); + } catch (ArrayIndexOutOfBoundsException e) { + throw new DukeException("EVENT_LENGTH_ERROR"); + } catch (Exception e) { + throw new DukeException("INVALID_TIME_FORMAT"); + } + //anomaly checker + EventDateTime addEvent = new EventDateTime(date, time); + for (Task task : taskArrayList) { + if (task.getType().equals(Action.EVENT)) { + EventDateTime existEvent = new EventDateTime(task.getDateTime(), task.getTime()); + if (addEvent.isAnomaly(existEvent)) { + printClash(task); + clash = true; + break; + } + } + } + if (!clash) { + taskArrayList.add(new Event(input[0], date, time)); + printAdd(); + } + } + + /** + * Modify the task to be set as done. + * taskNumber is parsed into an integer and modifies the task in the array + * of parsed integer to be set as done. + * + * @param taskNumber number to determine which task to modify + * @throws NotFoundException if the taskNumber is not within array size + */ + public void taskDone(String taskNumber) throws NotFoundException { + Integer inputNumber = Integer.parseInt(taskNumber); + assert inputNumber > 0; + if (taskArrayList.size() < inputNumber) { + throw new NotFoundException(); + } + taskArrayList.get(inputNumber - 1).setDone(); + } + + /** + * Deletes the task. + * taskNumber is parsed into an integer and gets the task in the array + * of parsed integer to be deleted and stores it first. + * Upon successful deletion, the deleted task in printed. + * + * @param taskNumber number to determine which task to delete + * @throws NotFoundException if the taskNumber is not within array size + */ + public void taskDelete(String taskNumber) throws NotFoundException { + Integer inputNumber = Integer.parseInt(taskNumber); + if (taskArrayList.size() < inputNumber) { + throw new NotFoundException(); + } + recentDelete = taskArrayList.get(inputNumber - 1); + taskArrayList.remove(inputNumber - 1); + printDelete(); + } + + /** + * Prints out all the task in the taskArrayList. + */ + public void printList() { + if (taskArrayList.size() == 0) { //0 items in list + System.out.println("List is empty!"); //throw empty list + } else { + int count = 1; + for (Task task : taskArrayList) { + System.out.println("" + (count) + "." + task); + count++; + } + } + } + + /** + * Prints out all the task that contains the search keyword(non-case sensitive). + * + * @param search the keyword to search for in description + */ + public void printSearchList(String search) { + int count = 1; + for (Task task : taskArrayList) { + if (task.getDescription().toLowerCase().contains(search.toLowerCase())) { + System.out.println("" + (count) + "." + task); + count++; + } + } + if (count == 1) { + System.out.println("Nothing found"); + } + } + + /** + * Prints out all the task that contains the search keyword(non-case sensitive) + * excluding those without a valid DateTime. + * + * @param searchDate the keyword to search for in date + */ + public void printSchedule(String searchDate) throws DukeException { + int count = 1; + try { + LocalDate date = LocalDate.parse(searchDate, DATE_FORMATTER); + for (Task task : taskArrayList) { + if (task.getDateTime() != null) { //excludes task without date + if (task.getDateTime().toLocalDate().equals(date)) { + System.out.println("" + (count) + "." + task); + count++; + } + } + } + if (count == 1) { + System.out.println("Nothing found"); + } + } catch (Exception e) { + throw new DukeException("INVALID_DATE_FORMAT"); + } + } + + public void saveList() { + for (Task task : taskArrayList) { + taskSave.add(task.getSave()); + } + } + + public ArrayList getSave() { + return taskSave; + } + + /** + * Print out the most recent task added. + */ + public void printAdd() { + System.out.println("Got it. Item successfully added to the list: "); + taskArrayList.get(taskArrayList.size() - 1).print(); + System.out.println("Now you have " + (taskArrayList.size()) + " task(s) in the list"); + } + + /** + * Print out the task that clash with the event trying to be added. + */ + public void printClash(Task clashTask) { + System.out.println("Event clashes with an existing event."); + clashTask.print(); + System.out.println("Delete existing event or change the timing."); + } + + /** + * Print out the most recent task deleted. + */ + public void printDelete() { + System.out.println("Noted. I have removed the task: "); + recentDelete.print(); + System.out.println("Now you have " + (taskArrayList.size()) + " task(s) in the list"); + } + + public int getArraySize() { + return taskArrayList.size(); + } + + +} + diff --git a/src/main/java/task/Task.java b/src/main/java/task/Task.java new file mode 100644 index 00000000..46ef8630 --- /dev/null +++ b/src/main/java/task/Task.java @@ -0,0 +1,73 @@ +package task; + +import java.time.LocalDateTime; +import java.time.LocalTime; + +abstract class Task { + protected String description; + protected Boolean isDone; + protected Action type; + + /** + * Constructor for a new task created by user command + * sets the description of the task to be description argument and isDone is always + * false for newly added task. + * + * @param description description of the task + */ + public Task(String description) { + setDescription(description); + isDone = false; + } + + /** + * Constructor for a new task created by loading file + * sets the description of the task to be description argument. + * isDone argument determines whether task isDone is true or false. + * + * @param description description of the task + * @param isDone is program set as done + */ + public Task(String description, Boolean isDone) { + setDescription(description); + this.isDone = isDone; + } + + public void setDescription(String description) { + this.description = description; + } + + + public abstract void setDone(); + + public abstract void print(); + + public abstract void setType(); + + public abstract String getTask(); + + public abstract String getDescription(); + + public abstract String getSave(); + + public LocalDateTime getDateTime() { + return null; + } + + public LocalTime getTime() { + return null; + } + + public Action getType() { + return type; + } + + public String getDone() { + if (isDone) { + return "1"; + } else { + return "0"; + } + } + +} diff --git a/src/main/java/task/Todo.java b/src/main/java/task/Todo.java new file mode 100644 index 00000000..4843fedb --- /dev/null +++ b/src/main/java/task/Todo.java @@ -0,0 +1,74 @@ +package task; + +public class Todo extends Task { + /** + * Constructor for a new to do created by user command + * sets the description of the task to be description argument and isDone is always + * false for newly added task. Type is set to TODO. + * + * @param description description of the task + */ + public Todo(String description) { + super(description); + setType(); + } + + /** + * Constructor for a new task created by loading file + * sets the description of the task to be description argument. + * isDone argument determines whether task isDone is true or false. + * + * @param description description of the task + */ + public Todo(String description, Boolean isDone) { + super(description); + this.isDone = isDone; + setType(); + } + + public void setDone() { + this.isDone = true; + System.out.println("Nice! I've marked this task as done:\n" + + " [T][X] " + getDescription()); + } + + public void setType() { + type = Action.TODO; + } + + public void print() { + if (isDone) { + System.out.println(" [T][X] " + getDescription()); + } else { + System.out.println(" [T][ ] " + getDescription()); + } + } + + public String getTask() { + return "T"; + } + + public String getDescription() { + return description; + } + + /** + * Returns the format of the task to be saved. + * gets task, isDone and description separated by ' | ' + * + * @return the file format + */ + public String getSave() { + String s = getTask() + " | " + getDone() + " | " + getDescription(); + return s; + } + + @Override + public String toString() { + String box = "[T][ ] "; + if (isDone) { + box = "[T][X] "; + } + return (box + getDescription()); + } +} diff --git a/src/main/java/ui/UI.java b/src/main/java/ui/UI.java new file mode 100644 index 00000000..ddacbb17 --- /dev/null +++ b/src/main/java/ui/UI.java @@ -0,0 +1,111 @@ +package ui; + +//import java.util.Scanner; + +public class UI { + private final String LINE_BREAK = "_______________________________________________________"; + private final String INPUT_FORMAT_ERROR = "'|' is not allowed as it is part of the save format."; + private final String TODO_DESCRIPTION_ERROR = "Todo is missing a description."; + private final String DEADLINE_DESCRIPTION_ERROR = "Deadline command is missing a description and/or deadline."; + private final String DEADLINE_LENGTH_ERROR = "Deadline command has too many /by."; + private final String EVENT_DESCRIPTION_ERROR = "Event command is missing a description and/or time."; + private final String EVENT_LENGTH_ERROR = "Event command has too many /at."; + private final String INVALID_DATETIME_FORMAT = "Incorrect Date formatting. Only accepts yyyy-MM-dd HHmm format " + + "\ne.g. 2021-10-21 1300"; + private final String INVALID_DATE_FORMAT = "Incorrect Date formatting. Only accepts yyyy-MM-dd HHmm format " + + "\ne.g. 2021-10-21"; + private final String INVALID_TIME_FORMAT = "Incorrect Time formatting. Only accepts H:mm format " + + "\ne.g. 1:30"; + private final String INVALID_ACTION = "Action not recognized. Please try again"; + + public void printIntro() { + printLine(); + System.out.println("Hello!"); + printInstruction(); + System.out.println("Welcome to Toh Shao Wei TIC2002 Project. Please enter your instruction. " + + "\nEnter help to see the list of instructions."); + printLine(); + } + + public void printInstruction() { + System.out.println("1.) Enter *todo 'text'* to add todo task."); // + System.out.println("2.) Enter *deadline 'text' /by 'DateTime'*to add deadline task."); + System.out.println("3.) Enter *event 'text' /at 'DateTime' /for 'Time'* to add event task."); + System.out.println("4.) Enter *list* to show list of tasks."); + System.out.println("5.) Enter *done 'integer'* mark task as done."); + System.out.println("6.) Enter *delete 'integer'* to delete task."); + System.out.println("7.) Enter *find 'text'* to search for the word."); + System.out.println("8.) Enter *view 'date'* to search all task on specific date."); + System.out.println("9.) Enter *bye* to save and exit program."); + System.out.println("10.) Enter *help* To see all the possible commands."); + printFormats(); + } + public void printFormats(){ + System.out.println("Program accepts date format : yyyy-MM-dd HHmm " + + "\ne.g. 2021-10-21 1300"); + System.out.println("Program accepts time format : H:mm " + + "\ne.g. 01:30"); + } + /* Unused after JavaFX implementation + public String readCommand() { + Scanner in = new Scanner(System.in); + return in.nextLine(); + } + */ + public void printExit() { + System.out.println("Bye. Hope to see you again soon!"); + System.out.println("Updating files."); + } + + public void printNotFound() { + System.out.println("Task cannot be found."); + } + + public void printInvalidEntry() { + System.out.println("Invalid task number entry."); + } + + + public void printLine() { + System.out.println(LINE_BREAK); + } + + public void showError(String errorMessage) { + System.out.print("Error occurred: "); + switch (errorMessage) { + case "INPUT_FORMAT_ERROR": + System.out.println(INPUT_FORMAT_ERROR); + break; + case "TODO_DESCRIPTION_ERROR": + System.out.println(TODO_DESCRIPTION_ERROR); + break; + case "DEADLINE_DESCRIPTION_ERROR": + System.out.println(DEADLINE_DESCRIPTION_ERROR); + break; + case "DEADLINE_LENGTH_ERROR": + System.out.println(DEADLINE_LENGTH_ERROR); + break; + case "EVENT_DESCRIPTION_ERROR": + System.out.println(EVENT_DESCRIPTION_ERROR); + break; + case "EVENT_LENGTH_ERROR": + System.out.println(EVENT_LENGTH_ERROR); + break; + case "INVALID_DATETIME_FORMAT": + System.out.println(INVALID_DATETIME_FORMAT); + break; + case "INVALID_DATE_FORMAT": + System.out.println(INVALID_DATE_FORMAT); + break; + case "INVALID_TIME_FORMAT": + System.out.println(INVALID_TIME_FORMAT); + break; + case "INVALID_ACTION": + System.out.println(INVALID_ACTION); + break; + default: + System.out.println("Unrecognized error"); + + } + } +} diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png new file mode 100644 index 00000000..1de1a76f 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 00000000..41cb81c5 Binary files /dev/null and b/src/main/resources/images/DaUser.png differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 00000000..ef64f84b --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 00000000..0c029d0a --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + +