diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..a98b760350 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,2 @@ +language: java + diff --git a/README.md b/README.md index 84755485a7..957fbd1adb 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Duke Increment | Tutorial ---------------|--------------- `A-Gradle` | [Gradle Tutorial](tutorials/gradleTutorial.md) -`A-TextUiTesting` | [Text UI Testing Tutorial](tutorials/textUiTestingTutorial.md) +`A-TextUiTesting` | [Text ui Testing Tutorial](tutorials/textUiTestingTutorial.md) `Level-10` | JavaFX tutorials:
→ [Part 1: Introduction to JavaFX][fx1]
→ [Part 2: Creating a GUI for Duke][fx2]
→ [Part 3: Interacting with the user][fx3]
→ [Part 4: Introduction to FXML][fx4] [fx1]: diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000000..c4192631f2 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..8041e4de56 --- /dev/null +++ b/build.gradle @@ -0,0 +1,68 @@ +plugins { + id 'java' + id 'application' + id 'checkstyle' + id 'org.openjfx.javafxplugin' version '0.0.7' + id 'com.github.johnrengelman.shadow' version '5.1.0' + +} + +checkstyle { + toolVersion = '8.23' +} + +group 'seedu.duke' +version '0.1.0' + +repositories { + mavenCentral() +} + +javafx { + version = "11.0.2" + modules = ['javafx.controls', 'javafx.fxml'] +} + +application { + // Change this to your main class. + mainClassName = "Launcher" +} + +dependencies { + String javaFxVersion = '11' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' + 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' + implementation group: 'org.openjfx', name: 'javafx-web', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-web', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-web', version: javaFxVersion, classifier: 'linux' + + testImplementation 'org.junit.jupiter:junit-jupiter:5.5.0' + + + +} + +shadowJar { + archiveBaseName = "duke" + archiveVersion = "0.1.3" + archiveClassifier = null + archiveAppendix = null +} + +run { + standardInput = System.in +} + 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/docs/README.md b/docs/README.md index fd44069597..18ddaf4d59 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,20 +1,187 @@ # User Guide +![Screenshot of Duke GUI](Ui.png) + ## Features -### Feature 1 -Description of feature. +### Keep track and manages all your tasks +Duke can help you manage all your todo, deadline and event tasks. + +### Saves Data +Duke can save all your tasks so that you can access them later. + +### Optimized for the Keyboard +Duke is fully usable with just a keyboard. Ideal for fast typers! ## Usage -### `Keyword` - Describe action +### Command Format + +* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `todo DESCRIPTION`, `DESCRIPTION` is a parameter which can be used as `todo borrow book`. +* Items in square brackets are optional e.g `event DESCRIPTION [/at DETAILS]` can be used as `event lecture` or as `event lecture /at LT19`. + +### `help` - Help Menu + +Displays a list of commands and usage that can be used. + +Format: + + `help` + +Expected outcome: + + Displays a help menu + +### `todo` - Adds a Todo Task + +Adds a Todo Task to your list of Tasks + +Format: + + `todo DESCRIPTION` + +Example of usage: + + `todo borrow a book` + +Expected outcome: + + Got it. I've added this task: + [T][✘] borrow a book + Now you have 1 tasks in this list + +### `event` - Adds an Event Task + +Adds an Event Task to your list of Tasks + +Format: + + `event DESCRIPTION [/at DETAILS]` + +Example of usage: + + `event baking contest` + `event attend a wedding /at Mandarin Hotel` + +Expected outcome: + + Got it. I've added this task: + [E][✘] baking contest + Now you have 2 tasks in this list + + Got it. I've added this task: + [E][✘] attend a wedding (at: Mandarin Hotel) + Now you have 3 tasks in this list + +### `deadline` - Adds an Deadline Task + +Adds a Deadline Task to your list of Tasks + +Format: + + `deadline [/by DETAILS]` + *A deadline will understand date and time if DETAILS is formatted as `dd/MM/yyyy HHmm`* + + +Example of usage: + + `deadline pay rent` + `deadline project submission /by Sunday` + `deadline return library book /by 21/09/2019 2359` + +Expected outcome: + + Got it. I've added this task: + [D][✘] pay rent + Now you have 4 tasks in this list + + Got it. I've added this task: + [D][✘] project submission (by: Sunday) + Now you have 5 tasks in this list + + Got it. I've added this task: + [D][✘] return library book (by: 2019-09-21T23:59) + Now you have 6 tasks in this list + +### `list` - Shows all your Tasks + +Shows all your Todos, Events and Deadlines together with their description, details and whether they are done or not. It also saves the current state of your list into Duke's storage. + +Format: + + `list` + +Expected outcome: + + 1. [T][✘] borrow a book + 2. [E][✘] baking contest + 3. [E][✘] attend a wedding (at: Mandarin Hotel) + 4. [D][✘] pay rent + 5. [D][✘] project submission (by: Sunday) + 6. [D][✘] return library book (by: 2019-09-21T23:59) + +### `find` - Find a Task + +Find a task based on a keyword supplied by the user + +Format: + + `find KEYWORD` + +Example of usage: + + `find book` + +Expected outcome: + + 1. [T][✘] borrow a book + 6. [D][✘] return library book (by: 2019-09-21T23:59) + +### `done` - Mark a Task as Done + +Marks a task as done by selecting the task number as shown when using the `list` or `find` command + +Format: + + `done TASK_NUMBER` + +Example of usage: + + `done 1` + +Expected outcome: + + Nice! I've marked this task as done: + [T][✔] borrow a book + +### `delete` - Delete a Task + +Deletes a task by selecting the task number as shown when using the `list` or `find` command + +Format: -Describe action and its outcome. + `delete TASK_NUMBER` Example of usage: -`keyword (optional arguments)` + `delete 1` Expected outcome: -`outcome` + Noted. I've removed this task: + [T][✔] borrow a book + You now have 5 tasks in this list + +### `bye` - Exits Duke + +Duke will close the application + +Format: + + `bye` + +Expected outcome: + + Duke application closes + + diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..23246191e9 Binary files /dev/null and b/docs/Ui.png differ diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000000..c4192631f2 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..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..8c14ea241d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Sep 04 03:32:52 SGT 2019 +distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME 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..4ab16d53c3 --- /dev/null +++ b/src/main/java/DialogBox.java @@ -0,0 +1,94 @@ +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.shape.Circle; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; + +public class DialogBox extends HBox { + private final Circle circleDisplay = new Circle(50, 50, 50); + + private Label text; + private ImageView displayPicture; + + /** + * DiaglogBox constructor. + * + * @param l labal + * @param iv imageView + */ + public DialogBox(Label l, ImageView iv) { + text = l; + displayPicture = iv; + + Label indentation = new Label(" "); + indentation.setFont(Font.font("Courier New", 15)); + + text.setWrapText(true); + displayPicture.setFitWidth(100.0); + displayPicture.setFitHeight(100.0); + displayPicture.setImage(iv.getImage()); + displayPicture.setClip(circleDisplay); + + this.setAlignment(Pos.TOP_RIGHT); + this.getChildren().addAll(text, indentation, displayPicture); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + this.setAlignment(Pos.TOP_LEFT); + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + FXCollections.reverse(tmp); + this.getChildren().setAll(tmp); + } + + /** + * DialogBox for the user. + * + * @param l label + * @param iv imageView + * @return DialogBox of the user + */ + public static DialogBox getUserDialog(Label l, ImageView iv) { + l.setFont(Font.font("Courier New", 15)); + var db = new DialogBox(l, iv); + db.setStyle("-fx-background-color: #E0E0E0"); + return db; + } + + /** + * DialogBox for Duke. + * + * @param l label + * @param iv imageView + * @return DialogBox for Duke + */ + public static DialogBox getDukeDialog(Label l, ImageView iv) { + l.setFont(Font.font("Courier New", 15)); + var db = new DialogBox(l, iv); + db.setStyle("-fx-background-color: #F4F4F4"); + db.flip(); + return db; + } + + /** + * DialogBox for welcome message. + * + * @param l label + * @param iv imageView + * @return DialogBox for welcome message + */ + public static DialogBox getWelcomeDialog(Label l, ImageView iv) { + l.setFont(Font.font("Courier New", FontWeight.BOLD, 20)); + var db = new DialogBox(l, iv); + db.setStyle("-fx-background-color: #F4F4F4"); + db.flip(); + return db; + } +} diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 5d313334cc..4469b15f93 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,10 +1,76 @@ +import logic.Command; +import logic.CommandParser; +import model.Tasklist; +import storage.Storage; +import ui.UI; +import ui.UI_CLI; + +import java.nio.file.Paths; + public class Duke { + private Storage storage; + private Tasklist tasks; + private UI ui; + + private final String INPUT_DELIMITER = " "; + private CommandParser commandParser = new CommandParser(INPUT_DELIMITER); + private boolean isExit; + public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); + new Duke("duke.txt", false).run(); + } + + /** + * Creates an instance of Duke with CLI or GUI. Duke will load and save data into the filepath. + * + * @param filepath the path of which data is saved and loaded from + * @param isGUI determines if GUI or CLI is loaded + */ + public Duke(String filepath, boolean isGUI) { + if (isGUI) { + ui = new UI_GUI(); + } else { + ui = new UI_CLI(); + } + assert ui != null : "UI not initiated"; + + storage = new Storage(Paths.get(filepath)); + tasks = storage.load(); + } + + /** + * Runs the program with CLI interface. + */ + public void run() { + ui.printWelcome(); + isExit = false; + + while (!isExit) { + String userInput = ui.getUserInput(); + String response = runCommand(userInput); + ui.printContent(response); + } + } + + /** + * Takes in input from user, parses the command, and executes it. + * + * @param userInput input from the user + * @return output of the command + */ + public String runCommand(String userInput) { + Command command = commandParser.parseCommand(userInput); + String response = command.execute(tasks, ui, storage); + isExit = command.isExit(); + return response; + } + + /** + * Checks if the program has exited. + * + * @return true if exited, false otherwise + */ + public boolean isExit() { + return isExit; } } diff --git a/src/main/java/Launcher.java b/src/main/java/Launcher.java new file mode 100644 index 0000000000..106f47140b --- /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(UI_GUI.class, args); + } +} \ No newline at end of file diff --git a/src/main/java/UI_GUI.java b/src/main/java/UI_GUI.java new file mode 100644 index 0000000000..b6fced2be9 --- /dev/null +++ b/src/main/java/UI_GUI.java @@ -0,0 +1,162 @@ +import ui.UI; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +public class UI_GUI extends Application implements UI { + private Duke duke; + private Image user = new Image(this.getClass().getResourceAsStream("/images/User.jpg")); + private Image duke_image = new Image(this.getClass().getResourceAsStream("/images/Duke.jpg")); + private ScrollPane scrollPane; + private VBox dialogContainer; + private TextField userInput; + private Button sendButton; + private Scene scene; + private boolean isExit; + + @Override + public void printWelcome() { + String welcome = "Hello! I'm Duke!\n" + + "What can I do for you? :)\n"; + Label dukeText = new Label(welcome); + dialogContainer.getChildren().addAll( + DialogBox.getWelcomeDialog(dukeText, new ImageView(duke_image)) + ); + } + + @Override + public String getUserInput() { + return null; + } + + @Override + public void printContent(String s) { + + } + + @Override + public void start(Stage stage) { + duke = new Duke("duke.txt", true); + + //The container for the content of the chat to scroll. + scrollPane = new ScrollPane(); + dialogContainer = new VBox(); + scrollPane.setContent(dialogContainer); + + userInput = new TextField(); + sendButton = new Button("Send"); + + AnchorPane mainLayout = new AnchorPane(); + mainLayout.getChildren().addAll(scrollPane, userInput, sendButton); + mainLayout.setStyle("-fx-background-color: #C0C0C0"); + + scene = new Scene(mainLayout); + + //Step 2. Formatting the window to look as expected + stage.setTitle("Duke"); + stage.setResizable(false); + stage.setMinHeight(800.0); + stage.setMinWidth(1100.0); + + mainLayout.setPrefSize(1100.0, 800.0); + + scrollPane.setPrefSize(1085, 735); + scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS); + + scrollPane.setVvalue(1.0); + scrollPane.setFitToWidth(true); + + // You will need to import `javafx.scene.layout.Region` for this. + dialogContainer.setPrefHeight(Region.USE_COMPUTED_SIZE); + + userInput.setPrefWidth(1025.0); + + sendButton.setPrefWidth(55.0); + + AnchorPane.setTopAnchor(scrollPane, 1.0); + + AnchorPane.setBottomAnchor(sendButton, 1.0); + AnchorPane.setRightAnchor(sendButton, 1.0); + + AnchorPane.setLeftAnchor(userInput, 1.0); + AnchorPane.setBottomAnchor(userInput, 1.0); + + //Step 3. Add functionality to handle user input. + sendButton.setOnMouseClicked((event) -> { + dialogContainer.getChildren().add(getDialogLabel(userInput.getText())); + userInput.clear(); + }); + + userInput.setOnAction((event) -> { + dialogContainer.getChildren().add(getDialogLabel(userInput.getText())); + userInput.clear(); + }); + + dialogContainer.heightProperty().addListener((observable) -> scrollPane.setVvalue(1.0)); + + //Part 3. Add functionality to handle user input. + sendButton.setOnMouseClicked((event) -> { + isExit = handleUserInput(); + if (duke.isExit()) { + try { + //Thread.sleep(1000); + } catch (Exception E) { + System.out.println("Wait failed"); + } + Platform.exit(); + } + }); + + userInput.setOnAction((event) -> { + isExit = handleUserInput(); + if (duke.isExit()) { + try { + //Thread.sleep(1000); + } catch (Exception E) { + System.out.println("Wait failed"); + } + Platform.exit(); + } + }); + + stage.setScene(scene); + stage.show(); + + printWelcome(); + } + + private Label getDialogLabel(String text) { + // You will need to import `javafx.scene.control.Label`. + Label textToAdd = new Label(text); + textToAdd.setWrapText(true); + + return textToAdd; + } + + private boolean handleUserInput() { + Label userText = new Label(userInput.getText()); + Label dukeText = new Label(getResponse(userInput.getText())); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(userText, new ImageView(user)), + DialogBox.getDukeDialog(dukeText, new ImageView(duke_image)) + ); + userInput.clear(); + return duke.isExit(); + } + + private String getResponse(String input) { + String response = duke.runCommand(input); + return response; + } +} diff --git a/src/main/java/logic/Command.java b/src/main/java/logic/Command.java new file mode 100644 index 0000000000..e2ff4f2ba3 --- /dev/null +++ b/src/main/java/logic/Command.java @@ -0,0 +1,26 @@ +package logic; + +import model.Tasklist; +import storage.Storage; +import ui.UI; + +public interface Command { + + /** + * Executes the Command. + * + * @param tasks the TaskList of Tasks + * @param ui The User Interface + * @param storage Storage + * @return a String that represents the response of the executed command + */ + String execute(Tasklist tasks, UI ui, Storage storage); + + /** + * Checks if the Command executes would result in an exit. + * + * @return true if it is an exit command, false otherwise + */ + boolean isExit(); + +} diff --git a/src/main/java/logic/CommandParser.java b/src/main/java/logic/CommandParser.java new file mode 100644 index 0000000000..e3eef48f42 --- /dev/null +++ b/src/main/java/logic/CommandParser.java @@ -0,0 +1,74 @@ +package logic; + +public class CommandParser { + private final String DELIMITER; + + private final String TODO = "todo"; + private final String EVENT = "event"; + private final String DEADLINE = "deadline"; + private final String EXIT = "bye"; + private final String LIST = "list"; + private final String DONE = "done"; + private final String DELETE = "delete"; + private final String FIND = "find"; + private final String HELP = "help"; + + /** + * Creates an instance of CommandParser with a specified delimiter. + * + * @param delimiter a string at which command arguments are separated by + */ + public CommandParser(String delimiter) { + this.DELIMITER = delimiter; + } + + /** + * Parses the command string and outputs the resulting command. + * + * @param s String to be pared + * @return resulting Command + */ + public Command parseCommand(String s) { + try { + String[] sp = s.split(DELIMITER, 2); + return parseName(sp[0], sp[1]); + + } catch (Exception E) { + return parseName(s, null); + } + + } + + /** + * Parses the command type and its arguments and outputs the corresponding Command. + * + * @param commandType type of the command as a String + * @param arguments arguments of that command + * @return the resulting Command + */ + private Command parseName(String commandType, String arguments) { + assert commandType != null : "commandType should not be null"; + + if (commandType.equals(EXIT)) { + return new ExitCommand(); + } else if (commandType.equals(LIST)) { + return new ListCommand(); + } else if (commandType.equals(DONE)) { + return new DoneCommand(arguments); + } else if (commandType.equals(DELETE)) { + return new DeleteCommand(arguments); + } else if (commandType.equals(TODO)) { + return new TodoCommand(arguments); + } else if (commandType.equals(EVENT)) { + return new EventCommand(arguments); + } else if (commandType.equals(DEADLINE)) { + return new DeadlineCommand(arguments); + } else if (commandType.equals(FIND)) { + return new FindCommand(arguments); + } else if (commandType.equals(HELP)) { + return new HelpCommand(); + } else { + return new WrongCommand(); + } + } +} diff --git a/src/main/java/logic/DeadlineCommand.java b/src/main/java/logic/DeadlineCommand.java new file mode 100644 index 0000000000..e651217293 --- /dev/null +++ b/src/main/java/logic/DeadlineCommand.java @@ -0,0 +1,71 @@ +package logic; + +import model.Tasklist; +import model.deadline; +import storage.Storage; +import ui.UI; + +import java.time.format.DateTimeFormatter; + +public class DeadlineCommand implements Command { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy HHmm"); + private String arguments; + private String description; + private String details; + + /** + * Creates an instance of DeadlineCommand with its arguments. + * + * @param arguments arguments of the Command + */ + public DeadlineCommand(String arguments) { + this.arguments = arguments; + try { + String[] sp = arguments.split("/by", 2); + this.description = sp[0].trim(); + this.details = sp[1].trim(); + } catch (Exception E) { + this.description = arguments; + this.details = null; + } + } + + /** + * Parses the arguments of the Command and executes it. + * + * @param tasks the TaskList of Tasks + * @param ui The User Interface + * @param storage Storage + * @return + */ + @Override + public String execute(Tasklist tasks, UI ui, Storage storage) { + String content = ""; + try { + if (arguments == null || arguments.trim().equals("")) { + content = "OOPS! The description of a deadline cannot be empty.\n" + + "Usage: deadline DESCRIPTION [/by DETAILS]\n"; + } else { + deadline task = new deadline(description, details); + tasks.add(task); + + content += "Got it. I've added this task:\n"; + if (details != null) { + content += "[" + task.getSymbol() + "][" + task.getIsDoneSymbol() + "] " + task.getDescription() + " (by: " + task.getTime() + ")\n"; + } else { + content += "[" + task.getSymbol() + "][" + task.getIsDoneSymbol() + "] " + task.getDescription() + "\n"; + } + content += "Now you have " + tasks.size() + " tasks in this list\n"; + } + } catch (Exception E) { + content = "Ohno something went wrong! :(\n"; + } + + return content; + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/logic/DeleteCommand.java b/src/main/java/logic/DeleteCommand.java new file mode 100644 index 0000000000..f1376a9c59 --- /dev/null +++ b/src/main/java/logic/DeleteCommand.java @@ -0,0 +1,65 @@ +package logic; + +import model.Tasklist; +import storage.Storage; +import ui.UI; + +public class DeleteCommand implements Command { + + private String arguments; + + /** + * Creates an instance of DeleteCommand with its arguments. + * + * @param arguments arguments of the Command + */ + public DeleteCommand(String arguments) { + this.arguments = arguments; + } + + /** + * Parses the arguments of the Command and executes it. + * + * @param tasks the TaskList of Tasks + * @param ui The User Interface + * @param storage Storage + * @return + */ + @Override + public String execute(Tasklist tasks, UI ui, Storage storage) { + String content = ""; + try { + int index = Integer.parseInt(arguments.trim()); + if (tasks.size() >= index) { + content += "Noted. I've removed this task:\n"; + content += "[" + tasks.get(index - 1).getSymbol() + "][" + tasks.get(index - 1).getIsDoneSymbol() + "] " + tasks.get(index - 1).getDescription(); + if (tasks.get(index - 1).getSymbol() == 'D') { + if (tasks.get(index - 1).getDetails() != null) { + content += " (by: " + tasks.get(index - 1).getTime() + ")"; + } + } else if (tasks.get(index - 1).getSymbol() == 'E') { + if (tasks.get(index - 1).getDetails() != null) { + content += " (at: " + tasks.get(index - 1).getDetails() + ")"; + } + } + content += "\n"; + tasks.remove(index - 1); + content += "You now have " + tasks.size() + " tasks in this list\n"; + } else { + content += "Failed to delete Task!\n" + + "No such Task found!\n"; + } + + } catch (Exception E) { + content = "Ohno! You have entered an invalid argument!\n" + + "Usage: delete TASK_NUMBER\n"; + } + + return content; + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/logic/DoneCommand.java b/src/main/java/logic/DoneCommand.java new file mode 100644 index 0000000000..17e7fe97fa --- /dev/null +++ b/src/main/java/logic/DoneCommand.java @@ -0,0 +1,61 @@ +package logic; + +import model.Tasklist; +import storage.Storage; +import ui.UI; + +public class DoneCommand implements Command { + private String arguments; + + /** + * Creates an instance of DoneCommand with its arguments. + * + * @param arguments arguments of the Command + */ + public DoneCommand(String arguments) { + this.arguments = arguments; + } + + /** + * Parses the arguments of the Command and executes it. + * + * @param tasks the TaskList of Tasks + * @param ui The User Interface + * @param storage Storage + * @return + */ + @Override + public String execute(Tasklist tasks, UI ui, Storage storage) { + String content = ""; + try { + int index = Integer.parseInt(arguments.trim()); + tasks.get(index - 1).markAsDone(); + + content += "Nice! I've marked this task as done:\n" + + "[" + tasks.get(index - 1).getSymbol() + "]" + + "[" + tasks.get(index - 1).getIsDoneSymbol() + "] " + tasks.get(index - 1).getDescription(); + + if (tasks.get(index - 1).getSymbol() == 'D') { + if (tasks.get(index - 1).getDetails() != null) { + content += " (by: " + tasks.get(index - 1).getTime() + ")"; + } + } else if (tasks.get(index - 1).getSymbol() == 'E') { + if (tasks.get(index - 1).getDetails() != null) { + content += " (at: " + tasks.get(index - 1).getDetails() + ")"; + } + } + content += "\n"; + } catch (Exception E) { + content = "Ohno! You have entered an invalid argument!\n" + + "Usage: done TASK_NUMBER\n"; + } + + + return content; + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/logic/EventCommand.java b/src/main/java/logic/EventCommand.java new file mode 100644 index 0000000000..068028dfa5 --- /dev/null +++ b/src/main/java/logic/EventCommand.java @@ -0,0 +1,69 @@ +package logic; + +import model.Task; +import model.Tasklist; +import model.event; +import storage.Storage; +import ui.UI; + +public class EventCommand implements Command { + private String arguments; + private String description; + private String details; + + /** + * Creates an instance of EventCommand with its arguments. + * + * @param arguments arguments of the Command + */ + public EventCommand(String arguments) { + this.arguments = arguments; + try { + String[] sp = arguments.split(" /at ", 2); + this.description = sp[0].trim(); + this.details = sp[1].trim(); + } catch (Exception E) { + this.description = arguments; + this.details = null; + } + } + + /** + * Parses the arguments of the Command and executes it. + * + * @param tasks the TaskList of Tasks + * @param ui The User Interface + * @param storage Storage + * @return + */ + @Override + public String execute(Tasklist tasks, UI ui, Storage storage) { + String content = ""; + try { + if (arguments == null || arguments.trim().equals("")) { + content = "OOPS!!! The description of a event cannot be empty.\n" + + "event DESCRIPTION [/at DETAILS]\n"; + } else { + Task task = new event(description, details); + tasks.add(task); + + content += "Got it. I've added this task:\n"; + if (details != null) { + content += "[" + task.getSymbol() + "][" + task.getIsDoneSymbol() + "] " + task.getDescription() + " (at: " + task.getDetails() + ")\n"; + } else { + content += "[" + task.getSymbol() + "][" + task.getIsDoneSymbol() + "] " + task.getDescription() + "\n"; + } + content += "Now you have " + tasks.size() + " tasks in this list\n"; + } + } catch (Exception E) { + content = "Ohno something went wrong! :(\n"; + } + + return content; + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/logic/ExitCommand.java b/src/main/java/logic/ExitCommand.java new file mode 100644 index 0000000000..76a7c73cdf --- /dev/null +++ b/src/main/java/logic/ExitCommand.java @@ -0,0 +1,35 @@ +package logic; + +import model.Tasklist; +import storage.Storage; +import ui.UI; + +public class ExitCommand implements Command { + + /** + * Creates an instance of ExitCommand. + */ + public ExitCommand() { + + } + + /** + * Parses the arguments of the Command and executes it. + * + * @param tasks the TaskList of Tasks + * @param ui The User Interface + * @param storage Storage + * @return + */ + @Override + public String execute(Tasklist tasks, UI ui, Storage storage) { + String content = "Bye. Hope to see you again soon!"; + return content; + } + + @Override + public boolean isExit() { + return true; + } + +} diff --git a/src/main/java/logic/FindCommand.java b/src/main/java/logic/FindCommand.java new file mode 100644 index 0000000000..76b6f8b3b2 --- /dev/null +++ b/src/main/java/logic/FindCommand.java @@ -0,0 +1,72 @@ +package logic; + +import model.Tasklist; +import storage.Storage; +import ui.UI; + +import java.util.ArrayList; + +public class FindCommand implements Command { + private String arguments; + + /** + * Creates an instance of FindCommand with its arguments. + * + * @param arguments arguments of the Command + */ + public FindCommand(String arguments) { + this.arguments = arguments; + } + + /** + * Parses the arguments of the Command and executes it. + * + * @param tasks the TaskList of Tasks + * @param ui The User Interface + * @param storage Storage + * @return + */ + @Override + public String execute(Tasklist tasks, UI ui, Storage storage) { + String content = ""; + if (this.arguments == null || this.arguments.trim().equals("")) { + content = "OOPS!!! The keyword of a find cannot be empty.\n" + + "Usage: find KEYWORD"; + } else { + ArrayList indexes = new ArrayList(); + int i; + for (i = 0; i < tasks.size(); i++) { + if (tasks.get(i).getDescription().contains(this.arguments)) { + indexes.add(i); + } + } + + if (indexes.size() == 0) { + content += "There are no Tasks that fits your description\n"; + } + + for (i = 0; i < indexes.size(); i++) { + content += (indexes.get(i) + 1) + ". "; + content += "[" + tasks.get(indexes.get(i)).getSymbol() + "]"; + content += "[" + tasks.get(indexes.get(i)).getIsDoneSymbol() + "]"; + content += " " + tasks.get(indexes.get(i)).getDescription(); + if (tasks.get(indexes.get(i)).getSymbol() == 'D') { + if (tasks.get(indexes.get(i)).getDetails() != null) { + content += " (by: " + tasks.get(indexes.get(i)).getTime() + ")"; + } + } else if (tasks.get(indexes.get(i)).getSymbol() == 'E') { + if (tasks.get(indexes.get(i)).getDetails() != null) { + content += " (at: " + tasks.get(indexes.get(i)).getDetails() + ")"; + } + } + content += "\n"; + } + } + return content; + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/logic/HelpCommand.java b/src/main/java/logic/HelpCommand.java new file mode 100644 index 0000000000..c22803715e --- /dev/null +++ b/src/main/java/logic/HelpCommand.java @@ -0,0 +1,42 @@ +package logic; + +import model.Tasklist; +import storage.Storage; +import ui.UI; + +public class HelpCommand implements Command { + + /** + * Creates an instance of HelpCommand. + */ + public HelpCommand() { + + } + + /** + * Parses the arguments of the Command and executes it. + * + * @param tasks the TaskList of Tasks + * @param ui The User Interface + * @param storage Storage + * @return + */ + @Override + public String execute(Tasklist tasks, UI ui, Storage storage) { + String content = "list Usage: list Lists out and saves all tasks\n" + + "todo Usage: todo DESCRIPTION Adds a todo task\n" + + "event Usage: event DESCRIPTION [/at DETAILS] Adds an event task\n" + + "deadline Usage: deadline DESCRIPTION [/by DETAILS] Adds a deadline task\n" + + "find Usage: find KEYWORD Searches for a task based on KEYWORD\n" + + "done Usage: done TASK_NUMBER marks the task with TASK_NUMBER as done\n" + + "delete Usage: delete TASK_NUMBER removes the task with TASK_NUMBER\n" + + "help Usage: help brings out the help menu\n" + + "bye Usage: bye Exits the program"; + return content; + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/logic/ListCommand.java b/src/main/java/logic/ListCommand.java new file mode 100644 index 0000000000..92419af387 --- /dev/null +++ b/src/main/java/logic/ListCommand.java @@ -0,0 +1,58 @@ +package logic; + +import model.Tasklist; +import storage.Storage; +import ui.UI; + +public class ListCommand implements Command { + + /** + * Creates an instance of ListCommand. + */ + public ListCommand() { + + } + + /** + * Parses the arguments of the Command and executes it. + * + * @param tasks the TaskList of Tasks + * @param ui The User Interface + * @param storage Storage + * @return + */ + @Override + public String execute(Tasklist tasks, UI ui, Storage storage) { + int i; + String content = ""; + if (tasks.size() == 0) { + content += "There are currently no tasks!"; + } + + for (i = 0; i < tasks.size(); i++) { + content += (i + 1) + ". "; + content += "[" + tasks.get(i).getSymbol() + "]"; + content += "[" + tasks.get(i).getIsDoneSymbol() + "]"; + content += " " + tasks.get(i).getDescription(); + if (tasks.get(i).getSymbol() == 'D') { + if (tasks.get(i).getDetails() != null) { + content += " (by: " + tasks.get(i).getTime() + ")"; + } + + } else if (tasks.get(i).getSymbol() == 'E') { + if (tasks.get(i).getDetails() != null) { + content += " (at: " + tasks.get(i).getDetails() + ")"; + } + } + content += "\n"; + } + storage.save(tasks); + + return content; + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/logic/TodoCommand.java b/src/main/java/logic/TodoCommand.java new file mode 100644 index 0000000000..45989892cb --- /dev/null +++ b/src/main/java/logic/TodoCommand.java @@ -0,0 +1,55 @@ +package logic; + +import model.Task; +import model.Tasklist; +import model.todo; +import storage.Storage; +import ui.UI; + +public class TodoCommand implements Command { + private String arguments; + + /** + * Creates an instance of TodoCommand with its arguments. + * + * @param arguments arguments of the Command + */ + public TodoCommand(String arguments) { + this.arguments = arguments; + } + + /** + * Parses the arguments of the Command and executes it. + * + * @param tasks the TaskList of Tasks + * @param ui The User Interface + * @param storage Storage + * @return + */ + @Override + public String execute(Tasklist tasks, UI ui, Storage storage) { + String content = ""; + try { + if (arguments == null || arguments.trim().equals("")) { + content = "OOPS! The description of a todo cannot be empty.\n" + + "Usage: todo DESCRIPTION\n"; + } else { + Task task = new todo(arguments.trim()); + tasks.add(task); + + content += "Got it. I've added this task:\n"; + content += "[" + task.getSymbol() + "][" + task.getIsDoneSymbol() + "] " + task.getDescription() + '\n'; + content += "Now you have " + tasks.size() + " tasks in this list\n"; + } + } catch (Exception E) { + content = "Ohno something went wrong! :(\n"; + } + + return content; + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/logic/WrongCommand.java b/src/main/java/logic/WrongCommand.java new file mode 100644 index 0000000000..9adc814131 --- /dev/null +++ b/src/main/java/logic/WrongCommand.java @@ -0,0 +1,18 @@ +package logic; + +import model.Tasklist; +import storage.Storage; +import ui.UI; + +public class WrongCommand implements Command { + @Override + public String execute(Tasklist tasks, UI ui, Storage storage) { + String content = "OOPS!!! I'm sorry, but I don't know what that means :-(\n"; + return content; + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/model/Task.java b/src/main/java/model/Task.java new file mode 100644 index 0000000000..b7e6675efc --- /dev/null +++ b/src/main/java/model/Task.java @@ -0,0 +1,49 @@ +package model; + +public abstract class Task { + private String description; + private boolean isDone; + + public Task(String description) { + this.description = description; + this.isDone = false; + } + + public String getDescription() { + return this.description; + } + + public boolean isDone() { + return this.isDone; + } + + public char getIsDoneSymbol() { + if (isDone) { + return '\u2714'; //check mark symbol + } else { + return '\u2718'; //cross mark symbol + } + } + + public void setIsDone(boolean isDone) { + this.isDone = isDone; + } + + public boolean markAsDone() { + return this.isDone = true; + } + + public boolean markAsUndone() { + return this.isDone = false; + } + + public char getSymbol() { + return '?'; + } + + public String getDetails() { + return null; + } + + public abstract String getTime(); +} diff --git a/src/main/java/model/Tasklist.java b/src/main/java/model/Tasklist.java new file mode 100644 index 0000000000..00c6648dd2 --- /dev/null +++ b/src/main/java/model/Tasklist.java @@ -0,0 +1,14 @@ +package model; + +import java.util.ArrayList; +import java.util.List; + +public class Tasklist extends ArrayList { + public Tasklist() { + super(); + } + + public Tasklist(List tasks) { + super(tasks); + } +} diff --git a/src/main/java/model/deadline.java b/src/main/java/model/deadline.java new file mode 100644 index 0000000000..fcb444f964 --- /dev/null +++ b/src/main/java/model/deadline.java @@ -0,0 +1,51 @@ +package model; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class deadline extends Task { + private final char symbol = 'D'; + private String details; + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy HHmm"); + private LocalDateTime time; + + public deadline(String description, String details) { + super(description); + this.details = details; + try { + System.out.println(details); + this.time = LocalDateTime.parse(details.trim(), DATE_TIME_FORMATTER); + } catch (Exception E) { + this.time = null; + } + } + + public deadline(String description, Boolean isDone, String details) { + super(description); + this.details = details; + try { + this.time = LocalDateTime.parse(details.trim(), DATE_TIME_FORMATTER); + } catch (Exception E) { + this.time = null; + } + this.setIsDone(isDone); + } + + @Override + public char getSymbol() { + return this.symbol; + } + + @Override + public String getDetails() { + return this.details; + } + + public String getTime() { + if (this.time == null) { + return getDetails(); + } else { + return this.time.toString(); + } + } +} diff --git a/src/main/java/model/event.java b/src/main/java/model/event.java new file mode 100644 index 0000000000..637a17ae5d --- /dev/null +++ b/src/main/java/model/event.java @@ -0,0 +1,34 @@ +package model; + +public class event extends Task { + private final char symbol = 'E'; + private String details; + + public event(String description, String details) { + super(description); + this.details = details; + } + + public event(String description, Boolean isDone, String details) { + super(description); + this.details = details; + this.setIsDone(isDone); + } + + @Override + public char getSymbol() { + return this.symbol; + } + + @Override + public String getDetails() { + return this.details; + } + + @Override + public String getTime() { + return getDetails(); + } + + +} diff --git a/src/main/java/model/todo.java b/src/main/java/model/todo.java new file mode 100644 index 0000000000..b9066b1b72 --- /dev/null +++ b/src/main/java/model/todo.java @@ -0,0 +1,24 @@ +package model; + +public class todo extends Task { + private final char symbol = 'T'; + + public todo(String description) { + super(description); + } + + public todo(String description, boolean isDone) { + super(description); + this.setIsDone(isDone); + } + + @Override + public char getSymbol() { + return this.symbol; + } + + @Override + public String getTime() { + return getDescription(); + } +} diff --git a/src/main/java/storage/Storage.java b/src/main/java/storage/Storage.java new file mode 100644 index 0000000000..4fb22bb663 --- /dev/null +++ b/src/main/java/storage/Storage.java @@ -0,0 +1,270 @@ +package storage; + +import model.Task; +import model.Tasklist; +import model.deadline; +import model.event; +import model.todo; + +import java.io.PrintWriter; +import java.io.Reader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Scanner; +import java.util.StringJoiner; + +public class Storage { + private final Path filePath; + private final String DELIMITER = "//"; + + /** + * Creates an instance of Storage to save and load data into the filepath. + * + * @param filePath path of which data is saved and loaded from + */ + public Storage(Path filePath) { + this.filePath = filePath; + } + + /** + * Creates and instance of Storage with no filepath. + */ + public Storage() { + this.filePath = null; + } + + /** + * Loads data from the filepath and outputs the TaskList of Tasks. + * + * @return a TaskList of Tasks + */ + public Tasklist load() { + return parseFromFile(this.filePath); + } + + /** + * Saves the current TaskList into the filepath. + * + * @param tasks TaskList to be saved + */ + public void save(Tasklist tasks) { + try { + serializeToFile(filePath, tasks); + } catch (Exception E) { + System.out.println("Failed to save file"); + } + } + + /** + * Attempts to read from filepath and returns it as a TaskList. + * It returns an empty TaskList if no filepath is specified or filepath is inaccessible. + * + * @param filepath filepath in which it is loaded from + * @return Resulting TaskList + */ + public Tasklist parseFromFile(Path filepath) { + Tasklist tasks = new Tasklist(); + try { + Reader reader = Files.newBufferedReader(filepath); + Scanner sc = new Scanner(reader); + while (sc.hasNextLine()) { + tasks.add(parseLine(sc.nextLine())); + } + } catch (Exception E) { + //System.out.println("Error in parsing file! Returning empty TaskList"); + } + return tasks; + } + + /** + * Parses the command and returns the resulting Task. + * + * @param line input command to be parsed + * @return resulting Task + */ + public Task parseLine(String line) { + //assert line != null: "input string cannot be null"; + + String[] sp = line.split(DELIMITER); + + Class taskType = parseTaskType(sp[0]); + String taskDescription = parseTaskDescription(sp[1]); + boolean taskIsDone = parseTaskIsDone(sp[2]); + + Task task = null; + if (taskType.equals(todo.class)) { + task = new todo(taskDescription, taskIsDone); + } else if (taskType.equals(deadline.class)) { + String taskDetails = parseTaskDetails(sp[3]); + task = new deadline(taskDescription, taskIsDone, taskDetails); + } else if (taskType.equals(event.class)) { + String taskDetails = parseTaskDetails(sp[3]); + task = new event(taskDescription, taskIsDone, taskDetails); + } + return task; + } + + /** + * Parses a string and outputs the corresponding task type. + * + * @param s string to be parsed into the corresponding task type + * @return resulting task type + */ + private Class parseTaskType(String s) { + if (s.equals("T")) { + return todo.class; + } else if (s.equals("D")) { + return deadline.class; + } else if (s.equals("E")) { + return event.class; + } else { + System.out.println("Error in resolving task type"); + return null; + } + } + + /** + * Parses a string and outputs the corresponding task description. + * + * @param s string to be parsed into the corresponding task description + * @return resulting task description + */ + private String parseTaskDescription(String s) { + return s; + } + + /** + * Parses a string and outputs the corresponding task isDone. + * + * @param s string to be parsed into the corresponding task isDone + * @return resulting task isDone + */ + private boolean parseTaskIsDone(String s) { + if (s.equals("Y")) { + return true; + } else if (s.equals("N")) { + return false; + } else { + System.out.println("Error in resolving task done"); + return false; + } + } + + /** + * Parses a string and outputs the corresponding task details. + * + * @param s string to be parsed into the corresponding task details + * @return resulting task details + */ + private String parseTaskDetails(String s) { + if (s.equals("null")) { + return null; + } else { + return s; + } + } + + /** + * writes data into the file. + * + * @param filePath filepath to write to + * @param tasks tasks to be written to filepath + */ + private void serializeToFile(Path filePath, Tasklist tasks) { + try { + PrintWriter writer = new PrintWriter(filePath.toFile(), "UTF-8"); + int i; + for (i = 0; i < tasks.size(); i++) { + writer.write(serializeLine(tasks.get(i))); + writer.println(); + } + writer.close(); + } catch (Exception E) { + System.out.println("Unable to write to file"); + } + } + + /** + * Formats and serializes a task into a String. + * + * @param task task to be formatted to a String + * @return a String that repersents the Task + */ + private String serializeLine(Task task) { + StringJoiner sj = new StringJoiner(DELIMITER); + + String taskType = serializeTaskType(task); + sj.add(taskType); + + String taskDescription = serializeTaskDescription(task); + sj.add(taskDescription); + + String taskIsDone = serializeTaskIsDone(task); + sj.add(taskIsDone); + + if (taskType.equals("D")) { + String taskDetails = serializeTaskDetails(task); + sj.add(taskDetails); + } else if (taskType.equals("E")) { + String taskDetails = serializeTaskDetails(task); + sj.add(taskDetails); + } + return sj.toString(); + } + + /** + * Serializes the Task type into a String. + * + * @param task Task to be serialized + * @return resulting String + */ + private String serializeTaskType(Task task) { + if (task instanceof todo) { + return "T"; + } else if (task instanceof deadline) { + return "D"; + } else if (task instanceof event) { + return "E"; + } else { + return null; + } + } + + /** + * Serializes the Task isDone into a String. + * + * @param task Task to be serialized + * @return resulting String + */ + private String serializeTaskIsDone(Task task) { + if (task.isDone()) { + return "Y"; + } else if (!task.isDone()) { + return "N"; + } else { + return "X"; + } + } + + /** + * Serializes the Task description into a String. + * + * @param task Task to be serialized + * @return resulting String + */ + private String serializeTaskDescription(Task task) { + return task.getDescription(); + } + + /** + * Serializes the Task details into a String. + * + * @param task Task to be serialized + * @return resulting String + */ + private String serializeTaskDetails(Task task) { + return task.getDetails(); + } + + +} diff --git a/src/main/java/ui/UI.java b/src/main/java/ui/UI.java new file mode 100644 index 0000000000..87b07c950e --- /dev/null +++ b/src/main/java/ui/UI.java @@ -0,0 +1,10 @@ +package ui; + +public interface UI { + + void printWelcome(); + + String getUserInput(); + + void printContent(String s); +} diff --git a/src/main/java/ui/UI_CLI.java b/src/main/java/ui/UI_CLI.java new file mode 100644 index 0000000000..a30299b081 --- /dev/null +++ b/src/main/java/ui/UI_CLI.java @@ -0,0 +1,45 @@ +package ui; + +import java.io.PrintStream; +import java.util.Scanner; + +public class UI_CLI implements UI { + + private final Scanner sc; + private final PrintStream output = System.out; + + public UI_CLI() { + sc = new Scanner(System.in); + } + + @Override + public void printWelcome() { + String logo = " ____ _ \n" + + "| _ \\ _ _| | _____ \n" + + "| | | | | | | |/ / _ \\\n" + + "| |_| | |_| | < __/\n" + + "|____/ \\__,_|_|\\_\\___|\n"; + println("Hello from\n" + logo); + printContent("Hello! I'm Duke\n" + + "What can I do for you?\n"); + } + + private void println(String s) { + output.println(s); + } + + + public void printContent(String content) { + printHorizontalLine(); + println(content); + printHorizontalLine(); + } + + public void printHorizontalLine() { + output.println("____________________________________________________________"); + } + + public String getUserInput() { + return sc.nextLine(); + } +} diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png new file mode 100644 index 0000000000..d893658717 Binary files /dev/null and b/src/main/resources/images/DaDuke.png differ diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png new file mode 100644 index 0000000000..3c82f45461 Binary files /dev/null and b/src/main/resources/images/DaUser.png differ diff --git a/src/main/resources/images/Duke.jpg b/src/main/resources/images/Duke.jpg new file mode 100644 index 0000000000..06979700e3 Binary files /dev/null and b/src/main/resources/images/Duke.jpg differ diff --git a/src/main/resources/images/User.jpg b/src/main/resources/images/User.jpg new file mode 100644 index 0000000000..834e6bd5b3 Binary files /dev/null and b/src/main/resources/images/User.jpg differ diff --git a/src/test/java/DeleteCommandTest.java b/src/test/java/DeleteCommandTest.java new file mode 100644 index 0000000000..250651d802 --- /dev/null +++ b/src/test/java/DeleteCommandTest.java @@ -0,0 +1,30 @@ +import logic.Command; +import logic.DeleteCommand; +import model.Tasklist; +import model.todo; +import storage.Storage; +import ui.UI; +import ui.UI_CLI; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DeleteCommandTest { + + @Test + void execute_delete_test() { + UI ui = new UI_CLI(); + Storage storage = new Storage(); + + Tasklist actualTasks = new Tasklist(); + actualTasks.add(new todo("Borrow a Book")); + + Command command = new DeleteCommand("1"); + String actualOutput = command.execute(actualTasks, ui, storage); + String expectedOutput = "Noted. I've removed this task:\n" + + "[T][x] Borrow a Book\n" + + "You now have 0 tasks in this list\n"; + + assertEquals(expectedOutput, actualOutput); + } +} diff --git a/src/test/java/TodoCommandTest.java b/src/test/java/TodoCommandTest.java new file mode 100644 index 0000000000..1c864992da --- /dev/null +++ b/src/test/java/TodoCommandTest.java @@ -0,0 +1,34 @@ +import logic.Command; +import logic.TodoCommand; +import model.Tasklist; +import model.todo; +import storage.Storage; +import ui.UI; +import ui.UI_CLI; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TodoCommandTest { + + @Test + void execute_todo_test() { + UI ui = new UI_CLI(); + Storage storage = new Storage(); + + Tasklist expectedTasks = new Tasklist(); + expectedTasks.add(new todo("Borrow a Book")); + + Tasklist actualTasks = new Tasklist(); + + Command command = new TodoCommand("Borrow a Book"); + String actualOutput = command.execute(actualTasks, ui, storage); + String expectedOutput = "Got it. I've added this task:\n" + + "[T][x] Borrow a Book\n" + + "Now you have 1 tasks in this list\n"; + + assertEquals(expectedTasks.get(0).getDetails(), actualTasks.get(0).getDetails()); + assertEquals(actualOutput, expectedOutput); + } + +} diff --git a/src/test/java/WrongCommandTest.java b/src/test/java/WrongCommandTest.java new file mode 100644 index 0000000000..4035601e89 --- /dev/null +++ b/src/test/java/WrongCommandTest.java @@ -0,0 +1,31 @@ +import logic.Command; +import logic.CommandParser; +import model.Tasklist; +import model.todo; +import storage.Storage; +import ui.UI; +import ui.UI_CLI; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class WrongCommandTest { + + @Test + void execute_wrong_test() { + UI ui = new UI_CLI(); + Storage storage = new Storage(); + + Tasklist expectedTasks = new Tasklist(); + + Tasklist actualTasks = new Tasklist(); + actualTasks.add(new todo("Borrow a Book")); + + CommandParser commandParser = new CommandParser(" "); + Command command = commandParser.parseCommand("This is a invalid command"); + String actualOutput = command.execute(actualTasks, ui, storage); + String expectedOutput = "OOPS!!! I'm sorry, but I don't know what that means :-(\n"; + + assertEquals(actualOutput, expectedOutput); + } +} diff --git a/tutorials/javaFxTutorialPart2.md b/tutorials/javaFxTutorialPart2.md index f24a0cd6ad..9369cf4b02 100644 --- a/tutorials/javaFxTutorialPart2.md +++ b/tutorials/javaFxTutorialPart2.md @@ -6,7 +6,7 @@ In this tutorial, we will be creating a GUI for Duke from scratch based on the f ## JavaFX controls -Controls are reusable UI elements. Refer to the [JavaFX's official documentation](https://openjfx.io/javadoc/11/javafx.controls/javafx/scene/control/package-summary.html) for a list of controls available. +Controls are reusable ui elements. Refer to the [JavaFX's official documentation](https://openjfx.io/javadoc/11/javafx.controls/javafx/scene/control/package-summary.html) for a list of controls available. From the mockup above, can you identify the controls that we will need to use? Mockup | Control @@ -19,7 +19,7 @@ Mockup | Control ## Designing the Layout -Now that we know what controls we need to implement our UI, let’s start programming! We quickly run into a problem: how do we show all of them on the screen at once? +Now that we know what controls we need to implement our ui, let’s start programming! We quickly run into a problem: how do we show all of them on the screen at once? Each scene is initialized with a root `Node`. In the previous tutorial, our root `Node` was a `Label`. What happens when we need to display more than one `Node` on the `Scene`? For that, we need to understand the JavaFX hierarchy. Recall from the previous tutorial: @@ -28,7 +28,7 @@ What happens when we need to display more than one `Node` on the `Scene`? For th From the diagram, you see that the root `Node` can contain many other `Nodes` and similarly, each of those `Nodes` can contain many other `Nodes`. This means that if we can find a _container_ to set as our root `Node`, we can place all our other `Nodes` in it. -But how do we get the exact layout we want in the UI? JavaFX provides that functionality in the form of **layout panes** in `javafx.scene.layouts`. Each layout pane follows a _layout policy_ to decide how to arrange its children. For example, the `VBox` lays out its children in a single vertical column and its counterpart, the `HBox` lays out its children in a single horizontal row. +But how do we get the exact layout we want in the ui? JavaFX provides that functionality in the form of **layout panes** in `javafx.scene.layouts`. Each layout pane follows a _layout policy_ to decide how to arrange its children. For example, the `VBox` lays out its children in a single vertical column and its counterpart, the `HBox` lays out its children in a single horizontal row. :bulb: A comprehensive list of layouts and how they behave is available here from the [official documentation](https://openjfx.io/javadoc/11/javafx.graphics/javafx/scene/layout/package-summary.html). diff --git a/tutorials/javaFxTutorialPart3.md b/tutorials/javaFxTutorialPart3.md index a9e1bdddd3..3f2cccdbc0 100644 --- a/tutorials/javaFxTutorialPart3.md +++ b/tutorials/javaFxTutorialPart3.md @@ -66,7 +66,7 @@ Verify that the `ScrollPane` scrolls as intended. ## Iteration 2 – Adding Dialog Boxes -In the mockup of the UI, notice that the dialog boxes are composed of two different controls (`ImageView` and `Label`) and reused multiple times. In situations like this, it is often beneficial to create our own custom control. +In the mockup of the ui, notice that the dialog boxes are composed of two different controls (`ImageView` and `Label`) and reused multiple times. In situations like this, it is often beneficial to create our own custom control. Let’s create our custom control `DialogBox`: ```java diff --git a/tutorials/javaFxTutorialPart4.md b/tutorials/javaFxTutorialPart4.md index 1a6e5bc412..4119517d29 100644 --- a/tutorials/javaFxTutorialPart4.md +++ b/tutorials/javaFxTutorialPart4.md @@ -101,8 +101,8 @@ We will get to that later. ## Using Controllers -As part of the effort to separate the code handling Duke's logic and UI, let's _refactor_ the UI-related code to its own class. -We call these UI classes _controllers_. +As part of the effort to separate the code handling Duke's logic and ui, let's _refactor_ the ui-related code to its own class. +We call these ui classes _controllers_. Let's implement the `MainWindow` controller class that we specified in `MainWindow.fxml`. @@ -160,7 +160,7 @@ public class MainWindow extends AnchorPane { ``` The `@FXML` annotation marks a `private` or `protected` member and makes it accessible to FXML despite its modifier. -Without the annotation, we will have to make everything `public` and expose our UI to unwanted changes. +Without the annotation, we will have to make everything `public` and expose our ui to unwanted changes. The `FXMLLoader` will map the a control with a `fx:id` defined in FXML to a variable with the same name in its controller. Notice how in `MainWindow`, we can invoke `TextField#clear()` on `userInput` and access its content just as we did in the previous example. @@ -168,7 +168,7 @@ Similarly, methods like private methods like `handleUserInput` can be used in FX ## Using FXML in our application -Let's create a new `Main` class as the bridge between the existing logic in `Duke` and the UI in `MainWindow`. +Let's create a new `Main` class as the bridge between the existing logic in `Duke` and the ui in `MainWindow`. **Main.java** ```java diff --git a/tutorials/textUiTestingTutorial.md b/tutorials/textUiTestingTutorial.md index f397d76aef..34d3db4832 100644 --- a/tutorials/textUiTestingTutorial.md +++ b/tutorials/textUiTestingTutorial.md @@ -1,4 +1,4 @@ -# Text UI Testing Tutorial +# Text ui Testing Tutorial 1. Create a folder `[project root]\text-ui-test` 1. Add a `runtest.bat` (if you are on Windows) or `runtest.sh` (if you are on a *nix OS) into the folder, containing the script below.