diff --git a/-diff b/-diff new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Duke.gradle b/Duke.gradle new file mode 100644 index 0000000000..e5b8ce7e49 --- /dev/null +++ b/Duke.gradle @@ -0,0 +1,16 @@ +plugins { + id 'java' + id 'application' +} + +group 'seedu.duke' +version '0.1.0' + +repositories { + mavenCentral() +} + +application { + // Change this to your main class. + mainClassName = "seedu.duke.Duke" +} diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..57da0fa3b3 --- /dev/null +++ b/build.gradle @@ -0,0 +1,63 @@ +plugins { + id 'java' + id 'application' + id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'checkstyle' +} + + + + +shadowJar { + archiveBaseName = "duke" + archiveVersion = "0.2.1" + archiveClassifier = null + archiveAppendix = null +} + +group 'seedu.duke' +version '0.2.1' + +repositories { + mavenCentral() +} + +checkstyle { + toolVersion = '8.23' +} + + +application { + mainClassName = 'Launcher' + run { + standardInput = System.in + } +} + + +dependencies { + String javaFxVersion = '11.0.2' + 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' + implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'linux' + testImplementation 'org.junit.jupiter:junit-jupiter:5.5.0' + } + +test { + useJUnitPlatform(); +} + + + diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..b1a57ba6c0 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,257 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/duke.txt b/data/duke.txt new file mode 100644 index 0000000000..189d9fc5a5 --- /dev/null +++ b/data/duke.txt @@ -0,0 +1,3 @@ +1 / ToDo / 0 / read book +2 / Deadline / 1 / homework / 12th of DECEMBER 1212, 12:12pm +3 / Event / 0 / jogging with friends / Wednesday diff --git a/diff b/diff new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/README.md b/docs/README.md index fd44069597..4176e6a514 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,20 +1,128 @@ # User Guide -## Features +[1. Introduction](README.md#Introduction) +[2. Quick Start](README.md#Quick-Start) +[3. Features](README.md#Features) +[4. FAQ](README.md#FAQ) +[5. Command Summary](README.md#Command-Summary) -### Feature 1 -Description of feature. +## 1. Introduction -## Usage +Duke is for those who prefer to use a desktop app for managing tasks. +More importantly, DukeBot is optimized for those who prefer to work with a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). Enjoy! -### `Keyword` - Describe action -Describe action and its outcome. +## 2. Quick Start +1. Ensure you have Java 11 or above installed in your Computer. -Example of usage: +2. Download the latest duke-0.2.1.jar [here](https://github.com/qweiping31415/duke/releases) -`keyword (optional arguments)` +![Ui.png](Ui.png) -Expected outcome: +## 3. Features -`outcome` +### 3.1. Viewing help +Shows a simple user guide to command formats. +Format: `help` + + +### 3.2. Adding a task: `todo`, `deadline`, `event` +Adds a task to the list + + +##### 3.2.1. Adding a ToDo Task: `todo` +Adds a ToDo task to the list +Format: `todo TASK_DESCRIPTION` + +Examples: +* `todo homework` +* `todo read book` + +##### 3.2.2. Adding a Deadline Task: `deadline` +Adds a Deadline task to the list +Format: `deadline TASK_DESCRIPTION /by DUE_DATETIME` + +Examples: +* `deadline do homework /by 3pm` +* `deadline do homework /by Monday` + +> ###### **Formatted date and time**: +> If `DUE_DATETIME` is entered in the `DD/MM/YYYY HHMM` format, it will automatically be converted to a more readable format. +> +>Example: +>`deadline do homework /by 12/12/1212 1212` + +##### 3.2.3. Adding a Event Task: `event` +Adds an Event task to Duke +Format: `event TASK_DESCRIPTION /by DUE_DATETIME` + +Example: +* `event consultation /at 3pm` +* `event consultation /at Monday` + +> ###### **Formatted date and time**: +> If `DUE_DATETIME` is entered in the `DD/MM/YYYY HHMM` format, it will automatically be converted to a more readable format. +> +>Example: +>`event consultation /at 12/12/1212 1212` + + +### 3.3. Finding tasks by keyword: `find` +Finds tasks whose descriptions contain an exact match of the given keyword. + +Format: `find KEYWORD` +Example: +* `find homework` + + +### 3.4. Marking a task as done: `done` +Marks the specified task in the list as done. + +Format: `done INDEX` +* Marks the tasks at the specified INDEX. +* The index refers to the index number shown in the displayed task list. +* The index must be a positive integer 1, 2, 3, …​ + + +### 3.5. Deleting a task: `delete` +Deletes the specified task from the list. +Format: `delete INDEX` +* Deletes the tasks at the specified INDEX. +* The index refers to the index number shown in the displayed task list. +* The index must be a positive integer 1, 2, 3, …​ + + +### 3.6. Listing all tasks : `list` +Shows a list of all tasks in the list. +Format: `list` + + +### 3.7. Exiting the program: `bye` +Exits the program. +Format: `bye` + + +## 4. FAQ +* None + + +## 5. Command Summary +* Add : `CODE_NAME TASK_DESCRIPTION [ADDITIONAL_KEYWORD] [DUE_DATETIME]` +e.g. `todo homework` +e.g. `deadline do homework /by 3pm` +e.g. `event consultation /at 12/12/1212 1212` + +* Find : `find KEYWORD` +e.g. `find homework` + +* Delete : `delete INDEX` +e.g. `delete 3` + +* Done : `done INDEX` +e.g. `done 3` + +* List : `list` + +* Help : `help` + +* Exit : `bye` diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..24ebc02260 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..87b738cbd0 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..4b7e1f3d38 --- /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-5.5.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..af6708ff22 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## 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"' + +# 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, switch paths to Windows format before running java +if $cygwin ; 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=$((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" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..6d57edc706 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@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 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" + +@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/settings.gradle b/settings.gradle new file mode 100644 index 0000000000..d1e92fe5db --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'duke' diff --git a/src/main/java/DialogBox.java b/src/main/java/DialogBox.java new file mode 100644 index 0000000000..49eda03543 --- /dev/null +++ b/src/main/java/DialogBox.java @@ -0,0 +1,105 @@ +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.*; + +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.paint.Color; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; + +import java.io.IOException; + +import javafx.geometry.Insets; +import java.util.Collections; + +/** + * 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. + */ + +//Some design for DialogBox.fxml +//adapted from https://github.com/calvincxz/duke/blob/master/src/main/resources/view/DialogBox.fxml +public class DialogBox extends HBox { + + @FXML + private Label dialog; + @FXML + private ImageView displayPicture; + + private static final Color DIALOGBOX_BACKGROUND_RED = Color.rgb(200,0,0); + private static final Color DIALOGBOX_BACKGROUND_BLUE = Color.rgb(0,0,200); + private static final Color DIALOGBOX_BACKGROUND_GREEN = Color.rgb(0,150,0); + + private static final CornerRadii DIALOGBOX_BACKGROUND_RADII = new CornerRadii(10); + + private static final Insets DIALOGBOX_BACKGROUND_INSET = new Insets(0,0,0,0); + + 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); + } + + // sets the background to a blue rounded corner box + public static DialogBox getUserDialog(String text, Image img) { + DialogBox user = new DialogBox(text, img); + user.setBackground( + new Background( + new BackgroundFill( + DIALOGBOX_BACKGROUND_BLUE, + DIALOGBOX_BACKGROUND_RADII, + DIALOGBOX_BACKGROUND_INSET))); + return user; + } + + //sets the background to green or red depending on error status + public static DialogBox getDukeDialog(String text, Image img, int errorStatus) { + DialogBox duke = new DialogBox(text, img); + + Color[] colours = new Color[]{DIALOGBOX_BACKGROUND_GREEN, + DIALOGBOX_BACKGROUND_RED}; + Color correctColor = colours[errorStatus]; + duke.setBackground( + new Background( + new BackgroundFill( + correctColor, + DIALOGBOX_BACKGROUND_RADII, + DIALOGBOX_BACKGROUND_INSET))); + + + duke.flip(); + + return duke; + } + + + + + +} \ No newline at end of file diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 5d313334cc..d98b68e31a 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,10 +1,49 @@ +import duke.commands.Command; +import duke.commands.CommandType; + +import duke.core.Storage; +import duke.core.Ui; +import duke.core.TaskList; +import duke.core.Parser; + +import duke.errors.DukeException; +import duke.tasks.Task; + +import java.io.IOException; + + +/** + * The driver class to run user interface of Duke. Duke provides commands to add different tasks, + * list out tasks, marking tasks as done, deleting tasks and storing the tasks into a file for + * retrieval after reboot + */ public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); + + private Ui ui; + private TaskList taskList; + + + /** + * Initialises a new Duke application. + * @throws DukeException Thrown when parts of the command cannot be executed. + */ + Duke() throws DukeException { + Storage storage = Storage.createStorageIfRequired(); + this.taskList = new TaskList(storage.load(), storage); + Task.setTaskList(taskList); + this.ui = new Ui(); } + + + Response getResponse(String input) { + try { + Command c = Parser.parseCommand(input); + return new Response(c.execute(taskList, ui),false); + } catch (IllegalArgumentException | DukeException | IOException error2) { + return new Response(ui.printErrorMessage(error2),true); + } + } + + } + diff --git a/src/main/java/Launcher.java b/src/main/java/Launcher.java new file mode 100644 index 0000000000..11dbf00c62 --- /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 0000000000..35a848aded --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,35 @@ +import duke.errors.DukeException; +import javafx.application.Application; +import javafx.stage.Stage; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; + +import java.io.IOException; + +/** + * A GUI for Duke using FXML. + */ +public class Main extends Application { + + @Override + public void start(Stage stage) throws DukeException { + try { + Duke duke = new Duke(); + + FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml")); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + stage.setScene(scene); + + fxmlLoader.getController().setDuke(duke); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} + + + diff --git a/src/main/java/MainWindow.java b/src/main/java/MainWindow.java new file mode 100644 index 0000000000..95766d6d94 --- /dev/null +++ b/src/main/java/MainWindow.java @@ -0,0 +1,75 @@ +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Dialog; +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 duke.core.Ui; + + +/** + * Controller for MainWindow. Provides the layout for the other controls. + */ + +//Some design for MainWindow.fxml +//adapted from https://github.com/calvincxz/duke/blob/master/src/main/resources/view/MainWindow.fxml +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")); + + private Ui ui = new Ui(); + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + } + + + public void setDuke(Duke d) { + duke = d; + dialogContainer.getChildren().add(DialogBox.getDukeDialog(ui.printWelcomeMessage(), dukeImage, 0)); + } + + /** + * 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(); + Response response = duke.getResponse(input); + int mode = booleanToInteger(response.getErrorStatus()); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(input, userImage), + DialogBox.getDukeDialog(response.getMessage(), dukeImage,mode) + ); + if (input.equals("bye")) { + Platform.exit(); + System.exit(0); + } + userInput.clear(); + } + + private int booleanToInteger(boolean isTrue) { + if (isTrue) { + return 1; + } else { + return 0; + } + } +} diff --git a/src/main/java/Response.java b/src/main/java/Response.java new file mode 100644 index 0000000000..2f66a8dbf9 --- /dev/null +++ b/src/main/java/Response.java @@ -0,0 +1,42 @@ +/** + * Represents a response from Duke due to some user input. + * A response provides the getter methods to its message and error status + */ +class Response { + + private String message; + private boolean isError; + + /** + * Initialises a Response that has a default isDone field of false. + * + * @param message Message to be displayed + * @param isError Error status tagged to the message + */ + Response(String message, boolean isError) { + this.message = message; + this.isError = isError; + } + + /** + * Returns the message to be shown to the user + * + * @return Message to be displayed + */ + String getMessage() { + return message; + } + + /** + * Returns the error status tagged to the message + * + * @return Error status + */ + boolean getErrorStatus() { + return isError; + } + + + + +} diff --git a/src/main/java/duke/commands/AddDeadlineCommand.java b/src/main/java/duke/commands/AddDeadlineCommand.java new file mode 100755 index 0000000000..491b0469ba --- /dev/null +++ b/src/main/java/duke/commands/AddDeadlineCommand.java @@ -0,0 +1,54 @@ +package duke.commands; + +import java.io.IOException; + + +import duke.core.TaskList; +import duke.core.Ui; + +import duke.errors.DukeAssertions; +import duke.tasks.Deadline; + + +/** + * Represents a command which contains an execute method that adds a deadline task to the task list. + * The AddDeadlineCommand object requires the parameters of the task that is to be + * added to the list. + */ +public class AddDeadlineCommand extends Command { + + private String description; + private String date; + + /** + * Initialises the add command which contains the parameters of the task to be created + * + * @param description deadline description + * @param date date description + */ + public AddDeadlineCommand(String description, String date) { + super(CommandType.COMMAND_ADD_DEADLINE); + this.description = description; + this.date = date; + + DukeAssertions.assertNotNull(description,date); + } + + /** + * Adds the deadline task to the task list and prints the result. + * + * @param taskList The main task list of the application. + * @param ui The main user interface of the application. + * @throws IOException Thrown when the new task cannot be added to the file. + */ + @Override + public String execute(TaskList taskList, Ui ui) throws IOException { + Deadline task = new Deadline(this.description,this.date); + + DukeAssertions.assertNotNull(taskList,ui); + + taskList.addToList(task); + return ui.printAddMessage(task, taskList); + } + +} diff --git a/src/main/java/duke/commands/AddEventCommand.java b/src/main/java/duke/commands/AddEventCommand.java new file mode 100755 index 0000000000..4994a52703 --- /dev/null +++ b/src/main/java/duke/commands/AddEventCommand.java @@ -0,0 +1,56 @@ +package duke.commands; + +import java.io.IOException; + + +import duke.core.TaskList; +import duke.core.Ui; + + +import duke.errors.DukeAssertions; +import duke.tasks.Event; + + +/** + * Represents a command which contains an execute method that adds a event task to the task list. + * The AddEventCommand object requires the parameters of the task that is to be + * added to the list. + */ +public class AddEventCommand extends Command{ + + private String description; + private String date; + + + /** + * Initialises the add command which contains the parameters of the task to be created + * Constructor to creating a command for adding an event task + * @param description event description + * @param date date description + */ + public AddEventCommand(String description, String date) { + super(CommandType.COMMAND_ADD_EVENT); + this.description = description; + this.date = date; + + DukeAssertions.assertNotNull(description,date); + } + + /** + * Adds the event task to the task list and prints the result. + * + * @param taskList The main task list of the application. + * @param ui The main user interface of the application. + * @throws IOException Thrown when the new task cannot be added to the file. + */ + public String execute(TaskList taskList, Ui ui) throws IOException { + Event task = new Event(this.description,this.date); + + DukeAssertions.assertNotNull(taskList,ui); + + taskList.addToList(task); + return ui.printAddMessage(task, taskList); + } + + +} diff --git a/src/main/java/duke/commands/AddToDoCommand.java b/src/main/java/duke/commands/AddToDoCommand.java new file mode 100755 index 0000000000..ed2fbcc5d8 --- /dev/null +++ b/src/main/java/duke/commands/AddToDoCommand.java @@ -0,0 +1,47 @@ +package duke.commands; + +import java.io.IOException; + +import duke.core.TaskList; +import duke.core.Ui; + +import duke.errors.DukeAssertions; +import duke.tasks.ToDo; + + +/** + * Represents a command which contains an execute method that adds a to-do task to the task list. + * The AddToDoCommand object requires the parameters of the task that is to be + * added to the list. + */ +public class AddToDoCommand extends Command{ + + private String [] tokens; + + /** + * Initialises the add command which contains the parameters of the task to be created + * + * @param tokens user input split by space, required for creating a to-do task + */ + public AddToDoCommand(String [] tokens) { + super(CommandType.COMMAND_ADD_TODO); + this.tokens = tokens; + + } + + /** + * Adds the to-do task to the task list and prints the result. + * + * @param taskList The main task list of the application. + * @param ui The main user interface of the application. + * @throws IOException Thrown when the new task cannot be added to the file. + */ + @Override + public String execute(TaskList taskList, Ui ui) throws IOException { + ToDo task = ToDo.createToDo(tokens); + DukeAssertions.assertNotNull(taskList,ui); + taskList.addToList(task); + return ui.printAddMessage(task, taskList); + } + +} diff --git a/src/main/java/duke/commands/Command.java b/src/main/java/duke/commands/Command.java new file mode 100755 index 0000000000..57bd8a46db --- /dev/null +++ b/src/main/java/duke/commands/Command.java @@ -0,0 +1,52 @@ +package duke.commands; + +import java.io.IOException; + +import duke.errors.DukeAssertions; +import duke.errors.DukeException; + +import duke.core.TaskList; +import duke.core.Ui; + +/** + * Base class for the other commands. Specifies the abstract method that is required to be implemented + * by the children commands. + */ +public abstract class Command { + private CommandType commandType; + + /** + * Initialises the command of a specific command type + * + * @param commandType The type of command to be created. + */ + public Command(CommandType commandType) { + this.commandType = commandType; + + DukeAssertions.assertNotNull(commandType); + } + + + /** + * getter for type of command + * + * @return type of command + */ + public CommandType getCommandType() { + return commandType; + } + + /** + * Executes what the command is suppose to do. + * + * @param taskList The main task list of the application. + * @param ui The main user interface of the application. + * @throws DukeException Occurs when parts of the command cannot be executed. + * @throws IOException Thrown when the file update fails. + */ + public abstract String execute(TaskList taskList, Ui ui) throws DukeException, IOException; + +} + + + diff --git a/src/main/java/duke/commands/CommandType.java b/src/main/java/duke/commands/CommandType.java new file mode 100644 index 0000000000..fb321e615b --- /dev/null +++ b/src/main/java/duke/commands/CommandType.java @@ -0,0 +1,18 @@ +package duke.commands; + +/** + * Represents the different command types + */ +public enum CommandType { + COMMAND_ADD_TODO, + COMMAND_ADD_EVENT, + COMMAND_ADD_DEADLINE, + COMMAND_SHOW_LIST, + COMMAND_DELETE_TASK, + COMMAND_DONE_TASK, + COMMAND_NULL, + COMMAND_EXIT, + COMMAND_FIND_TASK, + COMMAND_HELP +} + diff --git a/src/main/java/duke/commands/DeleteCommand.java b/src/main/java/duke/commands/DeleteCommand.java new file mode 100755 index 0000000000..95e534339e --- /dev/null +++ b/src/main/java/duke/commands/DeleteCommand.java @@ -0,0 +1,71 @@ +package duke.commands; + +import java.io.IOException; + +import duke.core.TaskList; +import duke.core.Ui; + +import duke.errors.DukeAssertions; +import duke.errors.DukeException; +import duke.errors.DukeExceptionType; + +import duke.tasks.Task; + +/** + * Represents a command which contains an execute method that deletes a task to the task list. + * The DeleteCommand object requires the task number of the task in the list that is to be + * deleted from the list. + */ + +public class DeleteCommand extends Command { + + private int index; + + + /** + * Initialises the command which contains the index of the task to be deleted + * @param index The index of the task to be deleted + */ + private DeleteCommand(int index){ + super(CommandType.COMMAND_DELETE_TASK); + this.index = index; + + assert index >= 0; + } + + /** + * Service for creating a delete command that checks for number formatting errors + * @param tokens User input split by space, required for creating a delete command + * @throws DukeException Thrown when the parameters does not specify the index of the task + */ + public static DeleteCommand createDeleteIfValid(String [] tokens) throws DukeException { + try { + int index = Integer.parseInt(tokens[1])-1; + return new DeleteCommand(index); + } catch (NumberFormatException error) { + throw new DukeException("Must be integer", DukeExceptionType.NOT_INTEGER); + } + } + + /** + * Deletes the specified task from the task list and prints the result. + * + * @param taskList The main task list of the application. + * @param ui The main user interface of the application. + * @throws IOException Thrown when the task cannot be removed from the file. + */ + @Override + public String execute(TaskList taskList, Ui ui) throws IOException { + assert ui != null; + try { + assert taskList != null; + + Task task = taskList.getTaskAt(index+1); + taskList.removeFromList(task); + return ui.printDeletion(task, taskList); + } catch (IndexOutOfBoundsException error3) { + return ui.printOneLine(new DukeException("No such task", DukeExceptionType.MISSING_TASK).getMessage()); + } + } + +} diff --git a/src/main/java/duke/commands/DoneCommand.java b/src/main/java/duke/commands/DoneCommand.java new file mode 100755 index 0000000000..ede8cc78cd --- /dev/null +++ b/src/main/java/duke/commands/DoneCommand.java @@ -0,0 +1,83 @@ +package duke.commands; + +import java.io.IOException; +import java.util.List; + + +import duke.core.TaskList; +import duke.core.Ui; + +import duke.errors.DukeException; +import duke.errors.DukeExceptionType; + +import duke.tasks.Task; + + +/** + * Represents a command which contains an execute method that marks a task in the task list as done. + * The DoneCommand object requires the task number of the task that is to be marked in the list. + */ +public class DoneCommand extends Command{ + + private int index; + + /** + * Initialises the command which contains the index of the task + * to be marked as done + * + * @param index the index of the task to be deleted + */ + private DoneCommand(int index){ + super(CommandType.COMMAND_DONE_TASK); + this.index = index; + assert index >= 0; + } + + + /** + * Service for creating a done command that checks for number formatting errors + * + * @param tokens User input split by space, required for creating a done command + * @throws DukeException Thrown when the parameters does not specify the index of the task + */ + public static DoneCommand createDoneIfValid(String [] tokens) throws DukeException { + try { + int index = Integer.parseInt(tokens[1])-1; + return new DoneCommand(index); + } catch (NumberFormatException error) { + throw new DukeException("Must be integer", DukeExceptionType.NOT_INTEGER); + } + } + + /** + * Executes by marking a particular task as done and prints to the user + * + * @param taskList The main task list of the application. + * @param ui The main user interface of the application. + * @throws IOException Thrown when the file update fails. + */ + @Override + public String execute(TaskList taskList, Ui ui) throws IOException { + assert ui != null; + try { + assert taskList != null; + + Task task = taskList.getTaskAt(index+1); + boolean isDoneBefore = task.setDone(); + if (isDoneBefore) { + throw new IllegalArgumentException("Task has already been done"); + } + taskList.setDoneInList(this.index+1); + + List inst = List.of("Nice! I've marked this task as done: ", + " "+task.toString()); + return ui.printInput(inst); + + } catch (IndexOutOfBoundsException error3) { + return ui.printOneLine(new DukeException("No such task", DukeExceptionType.MISSING_TASK).getMessage()); + } catch (IllegalArgumentException error2) { + return ui.printOneLine(new DukeException(error2.getMessage(), DukeExceptionType.TASK_ALREADY_DONE).getMessage()); + } + } + +} diff --git a/src/main/java/duke/commands/ExitCommand.java b/src/main/java/duke/commands/ExitCommand.java new file mode 100755 index 0000000000..40e7480c20 --- /dev/null +++ b/src/main/java/duke/commands/ExitCommand.java @@ -0,0 +1,31 @@ +package duke.commands; + +import duke.core.TaskList; +import duke.core.Ui; + +/** + * Represents a command which contains an execute method to exit the application. + */ +public class ExitCommand extends Command { + + /** + * Initialises the exit command + */ + public ExitCommand(){ + super(CommandType.COMMAND_EXIT); + } + + /** + * Prints the exit message. + * + * @param taskList The main task list of the application. + * @param ui The main user interface of the application. + */ + @Override + public String execute(TaskList taskList, Ui ui) { + assert ui != null; + return ui.printByeMessage(); + } + +} + diff --git a/src/main/java/duke/commands/FindCommand.java b/src/main/java/duke/commands/FindCommand.java new file mode 100644 index 0000000000..d105a26f37 --- /dev/null +++ b/src/main/java/duke/commands/FindCommand.java @@ -0,0 +1,62 @@ +package duke.commands; + +import java.util.List; + +import duke.core.TaskList; +import duke.core.Ui; +import duke.errors.DukeAssertions; +import duke.errors.DukeException; +import duke.errors.DukeExceptionType; +import duke.tasks.Task; + + + +/** + * Represents a command which contains an execute method that finds tasks with the matching keyword. + */ +public class FindCommand extends Command { + + private String keyword; + + + + + /** + * Initialises the find command which contains the keyword + * where the tasks will be searched against + * + * @param keyword Keyword to be searched against + */ + private FindCommand(String keyword) { + super(CommandType.COMMAND_FIND_TASK); + this.keyword = keyword; + assert keyword != null; + } + + /** + * Service for creating a find command that checks for multiple keywords + * @param tokens User input split by space, required for creating a find command + * @throws DukeException Thrown when the parameters contains multiple keywords + */ + public static FindCommand createFindCommandIfValid(String[] tokens) throws DukeException{ + if (tokens.length > 2) { + throw new DukeException("Must be a single keyword", DukeExceptionType.NOT_SINGLE_WORD); + } + return new FindCommand(tokens[1]); + } + + /** + * Executes by storing all tasks with descriptions containing the keyword + * and prints to the user + * @param taskList The main task list of the application. + * @param ui The main user interface of the application. + */ + public String execute(TaskList taskList, Ui ui) { + DukeAssertions.assertNotNull(taskList, ui); + List resultList = taskList.findTasks(this.keyword); + return ui.printFindResults(resultList); + } + + +} + diff --git a/src/main/java/duke/commands/HelpCommand.java b/src/main/java/duke/commands/HelpCommand.java new file mode 100644 index 0000000000..398dfed80e --- /dev/null +++ b/src/main/java/duke/commands/HelpCommand.java @@ -0,0 +1,29 @@ +package duke.commands; +import duke.core.TaskList; +import duke.core.Ui; +import duke.errors.DukeAssertions; + +/** + * Represents a command which contains an execute method that shows a help page. + */ +public class HelpCommand extends Command { + + /** + * Initialises the list command + */ + public HelpCommand(){ + super(CommandType.COMMAND_HELP); + } + + /** + * Shows a help page to the user + * + * @param taskList The main task list of the application. + * @param ui The main user interface of the application. + */ + @Override + public String execute(TaskList taskList, Ui ui) { + DukeAssertions.assertNotNull(taskList, ui); + return ui.printHelpMessage(); + } +} \ No newline at end of file diff --git a/src/main/java/duke/commands/ListCommand.java b/src/main/java/duke/commands/ListCommand.java new file mode 100755 index 0000000000..d32fc574c0 --- /dev/null +++ b/src/main/java/duke/commands/ListCommand.java @@ -0,0 +1,32 @@ +package duke.commands; + + +import duke.core.TaskList; +import duke.core.Ui; +import duke.errors.DukeAssertions; + + +/** + * Represents a command which contains an execute method that lists the tasks in the task list. + */ +public class ListCommand extends Command { + + /** + * Initialises the list command + */ + public ListCommand(){ + super(CommandType.COMMAND_SHOW_LIST); + } + + /** + * Lists all the tasks in the task list and prints them out. + * + * @param taskList The main task list of the application. + * @param ui The main user interface of the application. + */ + @Override + public String execute(TaskList taskList, Ui ui) { + DukeAssertions.assertNotNull(taskList, ui); + return ui.printNumberList(taskList); + } +} diff --git a/src/main/java/duke/commands/NullCommand.java b/src/main/java/duke/commands/NullCommand.java new file mode 100755 index 0000000000..94f10c090a --- /dev/null +++ b/src/main/java/duke/commands/NullCommand.java @@ -0,0 +1,35 @@ +package duke.commands; + + +import duke.core.TaskList; +import duke.core.Ui; + +import duke.errors.DukeException; +import duke.errors.DukeExceptionType; + +/** + * Represents a command that is invalid + */ +public class NullCommand extends Command{ + + /** + * Initialises the null command + */ + public NullCommand(){ + super(CommandType.COMMAND_NULL); + } + + /** + * Throws an exception which tells + * the user that the input command is not valid + * + * @param taskList The main task list of the application. + * @param ui The main user interface of the application. + * @throws DukeException Thrown when the command does not exist + */ + @Override + public String execute(TaskList taskList, Ui ui) throws DukeException { + throw new DukeException("Invalid Command! Please try again.", DukeExceptionType.INVALID_COMMAND); + } + +} diff --git a/src/main/java/duke/core/Parser.java b/src/main/java/duke/core/Parser.java new file mode 100644 index 0000000000..0c3c2783ed --- /dev/null +++ b/src/main/java/duke/core/Parser.java @@ -0,0 +1,254 @@ +package duke.core; + +import duke.commands.*; + +import duke.errors.DukeAssertions; +import duke.errors.DukeException; +import duke.errors.DukeExceptionType; + +import java.util.List; +import java.lang.StringBuilder; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.LocalDateTime; +import java.util.Arrays; + +/** + * Represents a class that takes in user inputs and translates them into different commands. + */ +public class Parser { + + private static String[] responses = new String[]{"/by","/at"}; + private static int[] startingIndex = new int[]{9,6}; + + /** + * Takes in user input and convert it into a command which performs a set of + * instructions on the task list and ui. + * + * @param input String that contains user input + * @return A command that execute a set of instructions. + * @throws IllegalArgumentException Thrown when the length of the command is not sufficient + * @throws DukeException Thrown when exceptions occur due to non-length checks + */ + public static Command parseCommand(String input) throws DukeException, IllegalArgumentException { + String[] tokens = input.split(" "); + if (tokens.length == 0) { + return new NullCommand(); + } else if (tokens[0].equals("bye")) { + return new ExitCommand(); + } else if (tokens[0].equals("list")) { + return new ListCommand(); + } else if (tokens[0].equals("help")) { + return new HelpCommand(); + } + checkValidLength(tokens); + + if (tokens[0].equals("done")) { + return DoneCommand.createDoneIfValid(tokens); + } else if (tokens[0].equals("delete")) { + return DeleteCommand.createDeleteIfValid(tokens); + } else if (tokens[0].equals("find")) { + return FindCommand.createFindCommandIfValid(tokens); + } else { + return Parser.createAddCommandIfValid(tokens, input); + } + } + + /** + * Throws an exception which tells + * the user that the input command is not valid, by checking the length of command + * the user that the input lacks information based on each case + * + * @param tokens User input split by space + * @throws IllegalArgumentException Thrown when the length of the command is not sufficient + */ + private static void checkValidLength(String[] tokens) throws IllegalArgumentException { + DukeAssertions.assertArrayNotEmpty(tokens); + List group1 = List.of("todo", "deadline", "event"); + List group2 = List.of("done", "delete"); + if (tokens.length == 1 && group1.contains(tokens[0])) { + throw new IllegalArgumentException(String.format("OOPS!!! The description of a %s cannot be empty.",tokens[0])); + } else if (tokens.length == 1 && group2.contains(tokens[0])) { + throw new IllegalArgumentException(String.format("OOPS!!! %s command requires integer.",tokens[0])); + } else if (tokens.length == 1 && tokens[0].equals("find")) { + throw new IllegalArgumentException(String.format("OOPS!!! %s command requires keyword input.",tokens[0])); + } + } + + // helper method to check if user input can still be a valid to-do, deadline or event task + private static Command createAddCommandIfValid(String[] tokens, String fullCommand) throws DukeException, IllegalArgumentException { + DukeAssertions.assertArrayNotEmpty(tokens); + + List validCommands = List.of("todo", "deadline", "event"); + + if (!validCommands.contains(tokens[0])) { + throw new DukeException("Command doesn't exist", DukeExceptionType.INVALID_COMMAND); + } + if (tokens[0].equals("todo")) { + return new AddToDoCommand(tokens); + } else if (tokens[0].equals("deadline")) { + return createDateCommandIfValid(tokens,fullCommand,0); + } else if (tokens[0].equals("event")) { + return createDateCommandIfValid(tokens,fullCommand,1); + } else { + return new NullCommand(); + } + } + + /** + * Takes in a string and tries to parse input string as Date and Time in dd/MM/yyyy HHmm, + * converting it into a more readable format + * eg. dd/MM/yyyy HHmm(e.g. 11/12/1111 1111 -> 11th of DECEMBER 1111, 11:11am) + * + * @param dateTimeString String to be parsed, and converted, if possible + * @return The formatted date and time, if it can be formatted + * @throws DukeException Thrown when the input cannot be formatted + */ + + //@@author qweiping31415-reused + //Reused from https://github.com/briyanii/duke/blob/master/src/main/java/duke/command/Parser.java + // with minor modifications + public static String parseDateTime(String dateTimeString) throws DukeException { + assert dateTimeString != null; + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HHmm"); + LocalDateTime dateAndTime = LocalDateTime.parse(dateTimeString, formatter); + + int year = dateAndTime.getYear(); + String month = dateAndTime.getMonth().toString(); + int day = dateAndTime.getDayOfMonth(); + int hour = dateAndTime.getHour(); + int minute = dateAndTime.getMinute(); + + StringBuilder dateTime = new StringBuilder(); + + dateTime.append(getFormattedDay(day)); + dateTime.append(" of "); + dateTime.append(month); + dateTime.append(" "); + dateTime.append(year); + dateTime.append(", "); + dateTime.append((hour > 12 ? hour - 12 : hour == 0 ? 12 : hour)); + if (minute != 0) { + dateTime.append(":"); + dateTime.append(minute); + } + if (hour < 12) { + dateTime.append("am"); + } else { + dateTime.append("pm"); + } + + return dateTime.toString(); + } catch (DateTimeParseException exception) { + throw new DukeException(dateTimeString + " is not in valid dd/MM/yyyy HHmm format.", + DukeExceptionType.INVALID_DATE_TIME_FORMAT); + } + } + + // helper method to attach a prefix to a day + private static String getFormattedDay(int day) { + assert day > 1; + int remainderHundred = day % 100; + if (remainderHundred > 9 && remainderHundred < 21) { + return day + "th"; + } else { + int remainderTen = day % 10; + switch (remainderTen) { + case 1: + return day + "st"; + case 2: + return day + "nd"; + case 3: + return day + "rd"; + default: + return day + "th"; + } + } + } + //@@author + + + + // helper method to check if the given date and time of a deadline or event task + // can be recognised as a DateTime format. + private static Command createDateCommandIfValid(String[] tokens, String fullCommand, int mode) + throws DukeException, IllegalArgumentException { + DukeAssertions.assertArrayNotEmpty(tokens); + assert fullCommand != null; + + List lst = Arrays.asList(tokens); + String key = responses[mode]; + if (!lst.contains(key)) { + throw new IllegalArgumentException("Missing deadline keyword!!"); + } + + int index = lst.indexOf(key); + checkTaskDescription(index); + String[] datedTaskSplit = fullCommand.split(" " + key + " "); + checkDeadline(datedTaskSplit); + String description = datedTaskSplit[0].substring(startingIndex[mode]); + String dateTime = datedTaskSplit[1]; + if (isDate(dateTime)) { + String dateTimeString = tokens[index + 1] + " " + tokens[index + 2]; + dateTime = Parser.parseDateTime(dateTimeString); + } + + return createDateCommand(mode, description, dateTime); + } + + //helper method to create the correct kind of DateTime Command + private static Command createDateCommand(int mode, String description, String correctDate) { + if (mode==0) { + return new AddDeadlineCommand(description,correctDate); + } else { + return new AddEventCommand(description,correctDate); + } + } + + //helper method to verify that the date is actually in dd/MM/yyyy HHmm format + private static boolean isDate(String dateDescription){ + assert dateDescription != null; + String[] dateSplit = dateDescription.split(" "); + if (dateSplit.length != 2){ + return false; + } else if (!dateSplit[0].contains("/") || + checkSlashCount(dateSplit[0])) { + return false; + } + return true; + } + + private static boolean checkSlashCount(String str) { + return str.chars() + .filter(ch -> ch == '/') + .count() != 2; + } + + //helper method to check if the datetime task can have a description + private static void checkTaskDescription(int index) throws IllegalArgumentException { + if (index -1 <=0) { + throw new IllegalArgumentException("Please input task description for DateCommand"); + } + + } + + //helper method to check if the datetime task is given a deadline + private static void checkDeadline(String [] datedTaskSplit) throws IllegalArgumentException { + if (datedTaskSplit.length > 2) { + throw new IllegalArgumentException("Multiple keyword detected!!"); + } + if (datedTaskSplit.length < 2) { + throw new IllegalArgumentException("Please insert a due date!!!"); + } + } + + + +} + + + + + + diff --git a/src/main/java/duke/core/Storage.java b/src/main/java/duke/core/Storage.java new file mode 100644 index 0000000000..3ec1af2f21 --- /dev/null +++ b/src/main/java/duke/core/Storage.java @@ -0,0 +1,160 @@ +package duke.core; + +import java.io.*; +import java.nio.file.Paths; +import java.util.Scanner; +import java.util.ArrayList; + +import duke.tasks.Task; +import duke.tasks.ToDo; +import duke.tasks.Deadline; +import duke.tasks.Event; + +import duke.errors.DukeException; +import duke.errors.DukeExceptionType; + + + + +/** + * Represents the storage of the application. Provides methods that create a storage, + * overwrite the contents of the file and loading data from the file. + */ +public class Storage{ + + private File file; + + /** + * Initialises the Storage with the file + * + * @param file File Object + */ + private Storage(File file) { + this.file = file; + } + + /** + * Creates a Storage for the application + * It creates a new directory with a new text file if there is no existing file + * + * @return Storage for the application + */ + public static Storage createStorageIfRequired() { + String path = System.getProperty("user.home"); + path += File.separator + "DukeData"; + File customDir = new File(path); + if (!customDir.exists()) { + customDir.mkdirs(); + } + + path += File.separator + "data.txt"; + File file = new File(path); + + if (!file.exists()) { + try { + file.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + return new Storage(file); + } + + + + /** + * Reads the data stored in the file, after which the date would be used + * to generate a ArrayList that would be returned. + * + * @return An ArrayList of tasks. + * @throws DukeException Thrown when the file does not exist. + */ + public ArrayList load() throws DukeException { + ArrayList taskList = new ArrayList<>(); + try { + Scanner sc = new Scanner(this.file); + while (sc.hasNext()) { + String line = sc.nextLine(); + taskList.add(formatFileToTask(line)); + } + return taskList; + } catch (FileNotFoundException e) { + throw new DukeException("Load failed", DukeExceptionType.FILE_NOT_FOUND); + } + } + + + /** + * Overwrites the data in the file by writing to the file. + * + * @param taskList Current task list stored in the application. + * @throws IOException Thrown when writing to file fails. + */ + void overwriteStorage(ArrayList taskList) throws IOException{ + assert (this.file != null); + + FileWriter fw = new FileWriter(this.file); + for (Task task: taskList){ + switch (task.getType()) { + case TODO_TASK: + fw.write(Task.getTaskID(task) + " / " + + "ToDo" + " / " + + task.getStorageStatusIcon() + " / " + + task.getDescription() + + System.lineSeparator()); + break; + case DEADLINE_TASK: + fw.write(Task.getTaskID(task) + " / " + + "Deadline" + " / " + + task.getStorageStatusIcon() + " / " + + task.getDescription() + " / " + + ((Deadline) task).getDate() + + System.lineSeparator()); + break; + case EVENT_TASK: + fw.write(Task.getTaskID(task) + " / " + + "Event" + " / " + + task.getStorageStatusIcon() + " / " + + task.getDescription() + " / " + + ((Event) task).getDate() + + System.lineSeparator()); + break; + default: + } + } + fw.close(); + } + + //helper method to convert the written format of the task in the file + //into a Task to be loaded back into storage + private Task formatFileToTask(String line) throws DukeException { + assert line != null; + String[] tokens = line.split(" / "); + switch(tokens[1]){ + case "ToDo": + ToDo toDoTask = new ToDo(tokens[3]); + if (tokens[2].equals("1")){ + toDoTask.setDone(); + } + return toDoTask; + case "Deadline": + Deadline deadlineTask = new Deadline(tokens[3], tokens[4]); + if (tokens[2].equals("1")){ + deadlineTask.setDone(); + } + return deadlineTask; + case "Event": + Event eventTask = new Event(tokens[3], tokens[4]); + if (tokens[2].equals("1")){ + eventTask.setDone(); + } + return eventTask; + default: + throw new DukeException("Unknown task detected? Something is wrong.", + DukeExceptionType.TASK_NOT_FOUND); + } + } + + +} \ No newline at end of file diff --git a/src/main/java/duke/core/TaskList.java b/src/main/java/duke/core/TaskList.java new file mode 100644 index 0000000000..01378a6760 --- /dev/null +++ b/src/main/java/duke/core/TaskList.java @@ -0,0 +1,139 @@ +package duke.core; + +import java.util.List; +import java.io.IOException; +import java.util.ArrayList; + +import duke.tasks.Task; + +/** + * Represents the task list used to store the tasks. Provides methods to add a task to the list, + * getting a List, getting a task using the index and removing + * a task based on the index specified, getting the size of the list, + * setting the done status of a task and getting the ID of a task + */ +public class TaskList { + + private ArrayList taskList; + private Storage storage; + + /** + * Initialises the TaskList, creates an ArrayList to store the tasks, + * and holds a reference to the main storage + * + * @param storage The main storage of the application. + */ + public TaskList(Storage storage){ + this.taskList = new ArrayList<>(); + this.storage = storage; + } + + /** + * Initialises the TaskList, with a given ArrayList to store the tasks, + * and holds a reference to the main storage + * + * @param list List to initialise with + * @param storage The main storage of the application. + */ + public TaskList(ArrayList list, Storage storage){ + this.taskList = list; + this.storage = storage; + } + + + /** + * Adds a task to the task list. + * + * @param task Task to be added. + * @throws IOException Thrown when writing to file fails. + */ + public void addToList(Task task) throws IOException { + this.taskList.add(task); + this.storage.overwriteStorage(taskList); + + } + + + /** + * Returns a ArrayList of type Task. + * + * @return An ArrayList of tasks. + */ + public ArrayList getList(){ + return this.taskList; + } + + + /** + * Returns a task at specified index. + * + * @param index Index of task to be retrieved. + * @return Task of the corresponding index. + */ + public Task getTaskAt(int index){ + return this.taskList.get(index - 1); + } + + + /** + * Removes a task from the list using its reference + * + * @param task Task to be removed. + * @throws IOException Thrown when writing to file fails. + */ + public void removeFromList(Task task) throws IOException { + boolean isRemoved = this.taskList.remove(task); + if (isRemoved) { + this.storage.overwriteStorage(taskList); + } + } + + + /** + * Sets task at specified index to done + * + * @param index Index of task to be set as done + * @throws IOException Thrown when writing to file fails. + */ + public void setDoneInList(int index) throws IOException { + this.taskList.get(index - 1).setDone(); + this.storage.overwriteStorage(taskList); + } + + /** + * Returns the number of tasks in current list + * + * @return Number of tasks in current list + */ + int getNumTasks(){ + return this.taskList.size(); + } + + /** + * Returns the position of the specified task in current list + * + * @return Position of the specified task in current list + */ + public int getTaskID(Task task) { + return taskList.indexOf(task) + 1; + } + + + /** + * Returns a list of tasks containing the specific keyword + * + * @return List of tasks containing the specific keyword + */ + public List findTasks(String word) { + ArrayList lst= new ArrayList<>(); + for (Task task: taskList) { + if (task.getDescription().contains(word)) { + lst.add(task); + } + } + return lst; + } + + +} + diff --git a/src/main/java/duke/core/Ui.java b/src/main/java/duke/core/Ui.java new file mode 100644 index 0000000000..f0cb77abdd --- /dev/null +++ b/src/main/java/duke/core/Ui.java @@ -0,0 +1,225 @@ +package duke.core; + +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +import duke.tasks.Task; + + +/** + * Represents the user interface of the application. Provides methods for reading input + * and printing output to the console. + */ +public class Ui { + + private Scanner sc = new Scanner(System.in); + + + /** + * Checks if there are inputs to be read + * + * @return True if there are inputs, false otherwise + */ + public boolean hasInputs() { + return sc.hasNextLine(); + } + + + /** + * Reads from standard input and returns it. + * + * @return String which consists of user input. + */ + public String readCommand() { + return sc.nextLine(); + } + + /** + * Prints the welcome message of the application. + * + * @return Welcome message. + */ + public String printWelcomeMessage() { + List start = new ArrayList<>(); + start.add("Hello! I'm Duke"); + start.add("What can I do for you?"); + return this.printInput(start); + } + + /** + * Prints a message when a task is added. + * + * @param task Task that was added. + * @param taskList Task list where the Task is stored. + + */ + public String printAddMessage(Task task, TaskList taskList) { + StringBuilder builder = new StringBuilder(); + + appendWithNewline(builder, "Got it. I've added this task: "); + appendWithNewline(builder, task.toString()); + appendWithNewline(builder, + String.format("Now you have %d tasks in the list.", taskList.getNumTasks())); + + return builder.toString(); + + + } + + /** + * Prints out the message when a task is deleted. + * + * @param task Task that was deleted. + * @param taskList Task list that the Task was removed from. + * @return String message to be printed. + */ + public String printDeletion(Task task, TaskList taskList) { + + StringBuilder builder = new StringBuilder(); + appendWithNewline(builder, "Noted. I've removed this task: "); + appendWithNewline(builder, task.toString()); + appendWithNewline(builder, + String.format("Now you have %d tasks in the list.", taskList.getNumTasks())); + + return builder.toString(); + + } + + + /** + * Prints a list of string in the correct format + * + * @param start List of Strings which needs to be printed sequentially + * @return String message to be printed. + */ + public String printInput(List start) { + StringBuilder builder = new StringBuilder(); + for (String input : start) { + appendWithNewline(builder, input); + } + + return builder.toString(); + + } + + /** + * Prints a single line of input in the correct format + * + * @param input String to be printed + * @return String message to be printed. + */ + public String printOneLine(String input) { + StringBuilder builder = new StringBuilder(); + appendWithNewline(builder, input); + + return builder.toString(); + + } + + /** + * Prints the list of tasks. + * + * @param taskList List of tasks stored in the application + * @return String message to be printed. + */ + public String printNumberList(TaskList taskList) { + + StringBuilder builder = new StringBuilder(); + appendWithNewline(builder, "Here are the tasks in your list:"); + for (int i = 0; i < taskList.getNumTasks(); i++) { + appendWithNewline(builder, + String.format("%d.%s", i + 1, taskList.getTaskAt(i + 1))); + } + + return builder.toString(); + } + + + /** + * Prints the list of tasks. + * + * @param lst List of tasks with matching keyword + * @return String message to be printed. + */ + public String printFindResults(List lst) { + + StringBuilder builder = new StringBuilder(); + + + builder.append("Here are the matching tasks in your list:"); + builder.append("\n"); + for (int i = 0; i < lst.size(); i++) { + appendWithNewline(builder, + String.format("%d.%s", i + 1, lst.get(i))); + } + + return builder.toString(); + } + + + /** + * Prints the exit message. + * + * @return String message to be printed. + */ + public String printByeMessage() { + + return this.printOneLine("Bye. Hope to see you again soon!"); + } + + + /** + * Prints out the exception. + * + * @param e Exception object whose message is to be printed out + */ + public String printErrorMessage(Exception e) { + return this.printOneLine(e.getMessage()); + } + + //helper method to attach a newline after adding a message to the same StringBuilder + private void appendWithNewline(StringBuilder builder, String msg) { + builder.append(msg); + builder.append("\n"); + } + + /** + * Prints out a mini user manual to help the user + * + * @return String message to be printed. + */ + public String printHelpMessage() { + return printInput(List.of( + "List of commands with format:", + "", + " todo TASK_DESCRIPTION : Adds a todo task to list", + " deadline TASK_DESCRIPTION /by dd/mm/yyyy hhmm : Adds a deadline to list " + + " with a recognisable datetime format", + " event TASK_DESCRIPTION /at dd/mm/yyyy hhmm : Adds a deadline to list" + + " with a recognisable datetime format", + " deadline TASK_DESCRIPTION /by DUE_DATE : To add deadline to list with any due date format", + " event TASK_DESCRIPTION /at DUE_DATE : To add event to list with any due date format", + " e.g. todo report", + " e.g. deadline report /by 12/12/1212 2312", + " e.g. event meetup /at 12/12/1212 2312", + " e.g. deadline report /by Monday", + " e.g. event meetup /at Monday", + "", + " find KEYWORD : Find tasks with the word in the description/name", + "", + " done [TASK_NUMBER] : Checks task at a particular index as done", + " delete [TASK_NUMBER] : Deletes task at a particular index", + "", + " list : Shows all undeleted tasks", + " bye : Exit the program", + "", + " Note: bracketed inputs like [TASK_NUMBER] denote that only integer inputs are accepted")); + } + + +} + + + + diff --git a/src/main/java/duke/errors/DukeAssertions.java b/src/main/java/duke/errors/DukeAssertions.java new file mode 100644 index 0000000000..434a972f8a --- /dev/null +++ b/src/main/java/duke/errors/DukeAssertions.java @@ -0,0 +1,25 @@ +package duke.errors; + +/** + * Represents an helper class to assert null checks for any number of objects + */ +public class DukeAssertions { + + /** + * Asserts that each object of any given number is not null + */ + public static void assertNotNull(Object ... items) { + for (Object item : items) { + assert item != null; + } + } + + /** + * Asserts that each String of any given number is not null + */ + public static void assertArrayNotEmpty(String [] items) { + for (String item : items) { + assert item != null; + } + } +} diff --git a/src/main/java/duke/errors/DukeException.java b/src/main/java/duke/errors/DukeException.java new file mode 100644 index 0000000000..1e470be6a1 --- /dev/null +++ b/src/main/java/duke/errors/DukeException.java @@ -0,0 +1,44 @@ +package duke.errors; + + +/** + * Represents an Exception class in Duke. + */ +public class DukeException extends Exception { + + private DukeExceptionType type; + + public DukeException(String error, DukeExceptionType type){ + super(error); + this.type = type; + + assert type != null; + } + + /** + * Specifies the message to be printed based on different Exception types + * @return String which is the message of the exception. + */ + public String getMessage() { + switch (type) { + case INVALID_COMMAND: + return "OOPS!!! I'm sorry, but I don't know what that means :-("; + case NOT_INTEGER: + return "Invalid input, must be an integer!!"; + case MISSING_TASK: + return "No such task"; + case INVALID_DATE_TIME_FORMAT: + return super.getMessage(); + case FILE_NOT_FOUND: + return "File not found!!"; + case TASK_NOT_FOUND: + return "Task not found!!"; + case NOT_SINGLE_WORD: + return "Must be a single keyword"; + case TASK_ALREADY_DONE: + return "Task already done!"; + default: + return "Unknown error! Please try again."; + } + } +} diff --git a/src/main/java/duke/errors/DukeExceptionType.java b/src/main/java/duke/errors/DukeExceptionType.java new file mode 100644 index 0000000000..ffe239fa05 --- /dev/null +++ b/src/main/java/duke/errors/DukeExceptionType.java @@ -0,0 +1,15 @@ +package duke.errors; + +/** + * Represents different Exception types + */ +public enum DukeExceptionType { + INVALID_COMMAND, + NOT_INTEGER, + MISSING_TASK, + FILE_NOT_FOUND, + TASK_NOT_FOUND, + NOT_SINGLE_WORD, + TASK_ALREADY_DONE, + INVALID_DATE_TIME_FORMAT +} diff --git a/src/main/java/duke/tasks/Deadline.java b/src/main/java/duke/tasks/Deadline.java new file mode 100644 index 0000000000..6cb5566421 --- /dev/null +++ b/src/main/java/duke/tasks/Deadline.java @@ -0,0 +1,44 @@ +package duke.tasks; + +/** + * Represents a deadline task in the application. + * A deadline provides the getter methods to its date. + */ +public class Deadline extends Task { + + private String date; + + /** + * Initialises an deadline task with the description and date and time of the deadline task. + * + * @param description Deadline description + * @param date Date description + */ + public Deadline(String description, String date) { + super(description, TaskType.DEADLINE_TASK); + this.date = date; + + assert date != null; + } + + /** + * Returns a string containing the date and time of a deadline task + * + * @return String containing the date and time of a deadline task. + */ + public String getDate() { + assert date != null; + return this.date; + } + + /** + * Returns A string that includes the task type, description and date of the deadline task. + * + * @return String that includes the task type, description and date of the deadline task. + */ + @Override + public String toString() { + return String.format("[D][%s] %s(by: %s)", getStatusIcon(), + getDescription(), getDate()); + } +} \ No newline at end of file diff --git a/src/main/java/duke/tasks/Event.java b/src/main/java/duke/tasks/Event.java new file mode 100644 index 0000000000..c41564ebbb --- /dev/null +++ b/src/main/java/duke/tasks/Event.java @@ -0,0 +1,44 @@ +package duke.tasks; + + +/** + * Represents an event task in the application. + * An event provides the getter methods to its date. + */ +public class Event extends Task { + + private String date; + + /** + * Initialises an deadline task with the description and date and time of the event task. + * + * @param description Event description + * @param date Date description + */ + public Event(String description, String date) { + super(description, TaskType.EVENT_TASK); + this.date = date; + assert date != null; + } + + /** + * Returns a string containing the date and time of a event task + * + * @return String containing the date and time of a event task. + */ + public String getDate() { + assert date != null; + return this.date; + } + + /** + * Returns A string that includes the task type, description and date of the event task. + * + * @return String that includes the task type, description and date of the event task. + */ + @Override + public String toString() { + return String.format("[E][%s] %s(at: %s)", getStatusIcon(), + getDescription(), getDate()); + } +} \ No newline at end of file diff --git a/src/main/java/duke/tasks/Task.java b/src/main/java/duke/tasks/Task.java new file mode 100644 index 0000000000..4ab9db22eb --- /dev/null +++ b/src/main/java/duke/tasks/Task.java @@ -0,0 +1,128 @@ +package duke.tasks; + +import duke.core.TaskList; +import duke.errors.DukeAssertions; + + +/** + * Represents a task in the application. A task has two private fields, the description of the task and + * the state of completion of the task. The type of task is package-private and taskList is a + * class attribute. The Task class provides the getters to type, description, completion status, + * the task list as well as getting the icon (tick and cross, or 1 and 0) + * which corresponds to the isDone field. Task class also + * supports a setDone method which sets isDone field to true and a setTaskList method which sets + * taskList field to the main task list of the application. + */ +public abstract class Task { + private String description; + private boolean isDone; + private TaskType type; + + public static TaskList taskList; + + /** + * Initialises a Task that has a default isDone field of false. + * + * @param description Description of the task. + * @param type Type of the task. + */ + public Task(String description, TaskType type) { + this.description = description; + this.type = type; + this.isDone = false; + + DukeAssertions.assertNotNull(description, type); + + } + + /** + * Returns the type of Task + * + * @return TaskType + */ + public TaskType getType() { + assert type != null; + return type; + } + + /** + * Returns a tick or a cross depending on the field isDone. + * + * @return Icon which shows a tick or a cross. + */ + public String getStatusIcon() { + return (isDone ? "\u2713" : "\u2718"); + } + + + /** + * Returns a 1 or 0 depending on the field isDone, used for Storage purposes. + * + * @return String number which is 1 or 0. + */ + public String getStorageStatusIcon() { + return (isDone ? "1" : "0"); + } + + /** + * Returns the description of the task. + * + * @return String that represents the description of the task. + */ + public String getDescription() { + assert description != null; + return description; + } + + /** + * Returns a string that includes the status icon and the description of the task. + * + * @return String with status icon and description of task. + */ + public String toString() { + return String.format("[%s] %s", getStatusIcon(), getDescription()); + } + + + /** + * Returns the type of Task + * + * @return Completion status of the task + */ + public boolean getDoneStatus() { + return isDone; + } + + + /** + * Sets the boolean isDone to true and returns the previous status of the task + * + * @return Previous boolean value of isDone. + */ + public boolean setDone() { + if (!isDone) { + this.isDone = true; + return false; + } else { + return true; + } + } + + /** + * Initialises a reference to the task list of the application in the Task class + */ + public static void setTaskList(TaskList taskList) { + Task.taskList = taskList; + assert taskList != null; + } + + /** + * Returns the position of the specified task in current list + * + * @return Position of the specified task in current list + */ + public static int getTaskID(Task task) { + assert taskList != null; + return Task.taskList.getTaskID(task); + } +} \ No newline at end of file diff --git a/src/main/java/duke/tasks/TaskType.java b/src/main/java/duke/tasks/TaskType.java new file mode 100644 index 0000000000..6e5f088508 --- /dev/null +++ b/src/main/java/duke/tasks/TaskType.java @@ -0,0 +1,11 @@ +package duke.tasks; + + +/** + * Represents different task types + */ +public enum TaskType { + EVENT_TASK, + TODO_TASK, + DEADLINE_TASK +} diff --git a/src/main/java/duke/tasks/ToDo.java b/src/main/java/duke/tasks/ToDo.java new file mode 100644 index 0000000000..5b01d3592e --- /dev/null +++ b/src/main/java/duke/tasks/ToDo.java @@ -0,0 +1,49 @@ +package duke.tasks; + +import java.lang.StringBuilder; + + +/** + * Represents a Todo task in the application. + * A Todo provides a factory method to create itself. + */ +public class ToDo extends Task { + + /** + * Initialises a todo task with the description of the task. + * + * @param description ToDo description + */ + public ToDo(String description) { + super(description, TaskType.TODO_TASK); + } + + + /** + * Creates a ToDo task from the parameters provided for a ToDo task + * + * @return A ToDo task + * @param tokens User input split by space + */ + public static ToDo createToDo(String [] tokens) { + StringBuilder builder = new StringBuilder(); + for (int i = 1; i < tokens.length - 1 ; i++) { + builder.append(tokens[i]); + builder.append(" "); + } + builder.append(tokens[tokens.length-1]); + return new ToDo(builder.toString()); + } + + + /** + * Returns A string that includes the type task and the toString of Task. + * + * @return String that adds the type of the task to the toString method of Task. + */ + @Override + public String toString() { + return String.format("[T][%s] %s", getStatusIcon(), getDescription()); + } + +} \ No newline at end of file diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png new file mode 100644 index 0000000000..d893658717 Binary files /dev/null and b/src/main/resources/images/DaDuke.png differ diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png new file mode 100644 index 0000000000..3c82f45461 Binary files /dev/null and b/src/main/resources/images/DaUser.png differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..6fa12924bc --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..804cf60670 --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/duke/core/ParserTest.java b/src/test/java/duke/core/ParserTest.java new file mode 100644 index 0000000000..52fc13503f --- /dev/null +++ b/src/test/java/duke/core/ParserTest.java @@ -0,0 +1,127 @@ +package duke.core; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import duke.core.Parser; +import duke.errors.DukeException; + +import duke.commands.Command; +import duke.commands.ExitCommand; +import duke.commands.ListCommand; +import duke.commands.DoneCommand; +import duke.commands.DeleteCommand; +import duke.commands.NullCommand; +import duke.commands.AddToDoCommand; +import duke.commands.AddDeadlineCommand; +import duke.commands.AddEventCommand; + +/** + * Class to test certain functionality of the Parser class + */ +class ParserTest { + + /** + * Test that the parseDateTime method can convert into + * formatted date and time when a valid input is given + */ + @Test + void parseDateTime_validInput_success() { + try { + assertEquals("2nd of DECEMBER 1212, 12pm", + Parser.parseDateTime("02/12/1212 1200")); + assertEquals("12th of DECEMBER 1212, 12:13am", + Parser.parseDateTime("12/12/1212 0013")); + assertEquals("23rd of FEBRUARY 2019, 11:12pm", + Parser.parseDateTime("23/02/2019 2312")); + } catch (DukeException e) { + fail("Should not have thrown exception"); + } + } + + /** + * Test that the parseDateTime method throws an exception + * when it tries to parse and format an invalid input + */ + @Test + void parseDateTime_invalidInput_exceptionThrown() { + assertThrows(DukeException.class, () -> Parser.parseDateTime("1212")); + assertThrows(DukeException.class, () -> Parser.parseDateTime("12/12/1212")); + assertThrows(DukeException.class, () -> Parser.parseDateTime("12pm")); + assertThrows(DukeException.class, () -> Parser.parseDateTime("Hello")); + } + + /** + * Test that the parseDateTime method throws an exception when the input + * is potentially valid but cannot be formatted to the expected format + */ + @Test + void parseDateTime_validLookingInvalidNumbers_exceptionThrown() { + assertThrows(DukeException.class, () -> Parser.parseDateTime("0/12/1212 1212")); + assertThrows(DukeException.class, () -> Parser.parseDateTime("-1/12/1212 1212")); + assertThrows(DukeException.class, () -> Parser.parseDateTime("/12/1212 1212")); + assertThrows(DukeException.class, () -> Parser.parseDateTime("99/12/1212 1212")); + assertThrows(DukeException.class, () -> Parser.parseDateTime("12/13/1212 1212")); + assertThrows(DukeException.class, () -> Parser.parseDateTime("12/12/999 1212")); + assertThrows(DukeException.class, () -> Parser.parseDateTime("12/12/0 1212")); + assertThrows(DukeException.class, () -> Parser.parseDateTime("12/12/0 00")); + } + + + /** + * Test that the parseCommand method throws the expected Exceptions when the it tries to parse a input + * as a command, but the required arguments are not provided + */ + @Test + void parseCommand_missingParameter_exceptionThrown() { + assertThrows(IllegalArgumentException.class, () -> Parser.parseCommand("todo ")); + + assertThrows(IllegalArgumentException.class, () -> Parser.parseCommand("deadline ")); + assertThrows(IllegalArgumentException.class, () -> Parser.parseCommand("deadline task")); + assertThrows(DukeException.class, () -> Parser.parseCommand("deadline /by time")); + + assertThrows(IllegalArgumentException.class, () -> Parser.parseCommand("event ")); + assertThrows(IllegalArgumentException.class, () -> Parser.parseCommand("event task ")); + assertThrows(DukeException.class, () -> Parser.parseCommand("event /at time")); + + assertThrows(IllegalArgumentException.class, () -> Parser.parseCommand("event task /at ")); + + assertThrows(IllegalArgumentException.class, () -> Parser.parseCommand("done ")); + assertThrows(IllegalArgumentException.class, () -> Parser.parseCommand("delete ")); + } + + /** + * Test that the parseCommand method properly still return an valid AddCommand + * even when the deadline is not in recognisable dateTime format + */ + @Test + void parseCommand_nonParseableTimeForDeadlineOrEvent_success() { + try { + assertTrue(Parser.parseCommand("deadline task /by time") instanceof AddDeadlineCommand); + assertTrue(Parser.parseCommand("event task /at time") instanceof AddEventCommand); + } catch (DukeException ex) { + fail("Should return without formatting the time parameter into dd/MM/yyyy HHmm"); + } + } + + /** + * Test that the parseCommand method ignores extra input when attempting to parse commands + * which do not require arguments. + */ + @Test + void parseCommand_extraArgumentForNoArgumentCommands_success() { + try { + assertTrue(Parser.parseCommand("list 1") instanceof ListCommand); + assertTrue(Parser.parseCommand("bye 1") instanceof ExitCommand); + assertTrue(Parser.parseCommand("list a") instanceof ListCommand); + assertTrue(Parser.parseCommand("bye a") instanceof ExitCommand); + } catch (DukeException ex) { + fail("Should return a command ignoring the extra arguments"); + } + } + +} \ No newline at end of file diff --git a/src/test/java/duke/core/TaskListTest.java b/src/test/java/duke/core/TaskListTest.java new file mode 100644 index 0000000000..d37f3458b6 --- /dev/null +++ b/src/test/java/duke/core/TaskListTest.java @@ -0,0 +1,24 @@ +package duke.core; + +import duke.errors.DukeException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import duke.tasks.Task; + +import duke.commands.Command; + + + + +class TaskListTest { + +} \ No newline at end of file diff --git a/tutorials/gradleTutorial.md b/tutorials/gradleTutorial.md index 08292b118d..335012a526 100644 --- a/tutorials/gradleTutorial.md +++ b/tutorials/gradleTutorial.md @@ -36,7 +36,7 @@ As a developer, you write a _build file_ that describes the project. A build fil mainClassName = "seedu.duke.Duke" } ``` -1. To check if Gradle has been added to the project correctly, open a terminal window, navigate to the root directory of your project and run the command `gradlew run`. This should result in Gradle running the main method of your project. +1. To check if Gradle has been added to the project correctly, open a terminal window, navigate to the root directory of your project and run the command `./gradlew run`. This should result in Gradle running the main method of your project. :bulb: Simply run the command `gradlew {taskName}` in the terminal and Gradle will run the task! Here are some example commands: * `gradlew tasks` (or `gradlew tasks --all`): shows a list of tasks available diff --git a/tutorials/javaFxTutorialPart2.md b/tutorials/javaFxTutorialPart2.md index f24a0cd6ad..c714f75ea0 100644 --- a/tutorials/javaFxTutorialPart2.md +++ b/tutorials/javaFxTutorialPart2.md @@ -49,6 +49,7 @@ import javafx.scene.layout.VBox; import javafx.stage.Stage; + public class Duke extends Application { private ScrollPane scrollPane; diff --git a/tutorials/javaFxTutorialPart3.md b/tutorials/javaFxTutorialPart3.md index a9e1bdddd3..2251efc04b 100644 --- a/tutorials/javaFxTutorialPart3.md +++ b/tutorials/javaFxTutorialPart3.md @@ -19,6 +19,7 @@ public void start(Stage stage) { //Step 2 code here //Step 3. Add functionality to handle user input. + sendButton.setOnMouseClicked((event) -> { dialogContainer.getChildren().add(getDialogLabel(userInput.getText())); userInput.clear();