diff --git a/.gitignore b/.gitignore index 99712178bf..c2f5aa01a2 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ src/main/resources/docs/ .DS_Store *.iml bin/ + +# Data files +/data/ diff --git a/README.md b/README.md deleted file mode 100644 index 84755485a7..0000000000 --- a/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Setting up - -**Prerequisites** - -* JDK 11 -* Recommended: IntelliJ IDE -* Fork this repo to your GitHub account and clone the fork to your computer - -**Importing the project into IntelliJ** - -1. Open IntelliJ (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project dialog first). -1. Set up the correct JDK version. - * Click `Configure` > `Structure for new Projects` (in older versions of Intellij:`Configure` > `Project Defaults` > `Project Structure`). - * If JDK 11 is listed in the drop down, select it. If it is not, click `New...` and select the directory where you installed JDK 11. - * Click `OK`. -1. Click `Import Project`. -1. Locate the project directory and click `OK`. -1. Select `Create project from existing sources` and click `Next`. -1. Rename the project if you want. Click `Next`. -1. Ensure that your src folder is checked. Keep clicking `Next`. -1. Click `Finish`. - -# Tutorials - -Duke Increment | Tutorial ----------------|--------------- -`A-Gradle` | [Gradle Tutorial](tutorials/gradleTutorial.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]: -[fx2]: -[fx3]: -[fx4]: - -# Feedback, Bug Reports - -* If you have feedback or bug reports, please post in [se-edu/duke issue tracker](https://github.com/se-edu/duke/issues). -* We welcome pull requests too. \ No newline at end of file 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..cce62d89a3 --- /dev/null +++ b/build.gradle @@ -0,0 +1,59 @@ +plugins { + id 'java' + id 'application' + id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'org.openjfx.javafxplugin' version '0.0.8' +} + +version '0.1.0' + +repositories { + mavenCentral() +} + +javafx { + version = "12.0.2" + modules = [ 'javafx.controls', 'javafx.fxml' ] +} + +application { + mainClassName = 'dose.Launcher' +} + +run { + standardInput = System.in +} + +dependencies { + String javaFxVersion = '12' + 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' +} + +test { + useJUnitPlatform() +} + +shadowJar { + archiveBaseName = "dose" + archiveVersion = "1.3" + archiveClassifier = null + archiveAppendix = null +} 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..c46cf18e9c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,20 +1,185 @@ # User Guide +**Dose** is a customisable personal assistant for managing your deadlines, events and todos. +Made for users who are comfortable with Command-Line based interactions, +Dose helps you get your tasks out of your head as quickly as possible without getting in the way. +Stop fiddling with bells and whistles of other todo-list apps -- Dose's pretty GUI is just a bonus! + +Experiencing a caffeinated rush and needing a dose of calm? +Stuck in a rut and wishing for a dose of motivation? Dose has your back. +Put your tasks in Dose and never forget about them ever again! ## Features -### Feature 1 -Description of feature. +### Add todos, events and deadlines +To add a todo, event or deadline to Dose, simply type the type of the task, +followed by its description and deadline (if any). Dose will update your list of tasks accordingly. + + + +### View tasks in task list +Keep track of the tasks you've added. +See each task's description, deadline, priority and tags (if any) in a compact list view. + + + +### Save task list to disk +Dose is capable of saving your list of tasks to a small, portable text file. +Upon the next startup of Dose, your tasks will be instantly re-imported -- no sweat! + + + +### Complete and delete tasks +Done with a task? Mistakenly added a task you no longer need? Dose will take care of it. + + + +### Tag and prioritise tasks +Need some organisation for your tasks? Dose is here to help. +(Sorting tasks by tag and priority is coming soon!) + + + +### Find tasks matching your query +Looking for a specific task? Simply search for it. + + + +### Snooze a task +Too many deadlines at your back? Have yourself a dose of calm by postponing non-urgent tasks. + + + +## Commands +1. [todo](#todo) +2. [deadline](#deadline) +3. [event](#event) +4. [list](#list) +5. [save](#save) +6. [done](#done) +7. [delete](#delete) +8. [tag](#tag) +9. [priority](#priority) +10. [find](#find) +11. [snooze](#snooze) +12. [bye](#bye) + +### `todo` +Creates a new todo. + +**Usage:** `todo [description]` + +**Parameters:** + +`[description]` Description of the todo (required). + +**Expected outcome:** +A new todo with the provided description is created. + +### `deadline` +Creates a new task with a deadline. + +**Usage:** `deadline [description] /by [deadline]` + +**Parameters:** + +`[description]` Description of the task (required). + +`[deadline]` Deadline of the task, in ISO format. + +**Expected outcome:** +A new deadline task with the provided description and deadline is created. + +### `event` +Creates a new event at a specified time. + +**Usage:** `event [description] /at [time]` + +**Parameters:** + +`[description]` Description of the event (required). + +`[time]` Time of the event, in ISO format. + +**Expected outcome:** +A new event with the provided description and date is created. + +### `list` +Displays the tasks in the task list. + +**Usage:** `list` + +**Expected outcome**: The current state of the task list is displayed. + +### `save` +Saves the tasks in the task list to the disk. + +**Usage:** `save` + +**Expected outcome:** The current state of the task list is saved to the disk. + +### `done` +Marks the task as complete. + +**Usage:** `done [taskId]` + +**Parameters:** +`[taskId]` ID of the task in the list (required). Use `list` to get this, if need be. + +### `delete` +Deletes the task. + +**Usage:** `delete [taskId]` + +**Parameters:** +`[taskId]` ID of the task in the list (required). Use `list` to get this, if need be. + +**Expected outcome:** The task is deleted from the list. + +### `tag` +Adds a tag to a task. + +**Usage:** `tag [taskId] [tag]` + +**Parameters:** +`[taskId]` ID of the task in the list (required). Use `list` to get this, if need be. +`[tag]` Tag to be added to the task. + +**Expected outcome:** The tag is added to the task. + +### `priority` +Adds a priority to a task. + +**Usage:** `priority [taskId] [priority]` + +**Parameters:** +`[taskId]` ID of the task in the list (required). Use `list` to get this, if need be. +`[priority]` Priority to be added to the task. Can be `low`, `medium` or `high`. + +**Expected outcome:** The tag is added to the task. + +### `find` +Find tasks matching a query. + +**Usage:** `find [keyword]` + +**Parameters:** +`[keyword]` Keyword to be used in searching for tasks. + +**Expected outcome:** Displays the list of tasks matching the query, if any. -## Usage +### `snooze` +Snoozes a given task. That is, postpones its deadline by one day. -### `Keyword` - Describe action +**Usage:** `snooze [taskId]` -Describe action and its outcome. +**Parameters:** +`[taskId]` ID of the task in the list (required). Use `list` to get this, if need be. -Example of usage: +**Expected outcome:** The deadline (of tasks with deadlines) or time (of events) is postponed by one day. -`keyword (optional arguments)` +### `bye` +Saves the tasks in the task list to the disk, then exits Dose. -Expected outcome: +**Usage:** `bye` -`outcome` +**Expected outcome:** The current state of the task list is saved to the disk. Duke can be safely exited. diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..ea24f42a11 Binary files /dev/null and b/docs/Ui.png differ diff --git a/docs/images/add_tasks.PNG b/docs/images/add_tasks.PNG new file mode 100644 index 0000000000..165f866b5f Binary files /dev/null and b/docs/images/add_tasks.PNG differ diff --git a/docs/images/done_delete_tasks.PNG b/docs/images/done_delete_tasks.PNG new file mode 100644 index 0000000000..62648a767d Binary files /dev/null and b/docs/images/done_delete_tasks.PNG differ diff --git a/docs/images/find_tasks.PNG b/docs/images/find_tasks.PNG new file mode 100644 index 0000000000..a8c19a7018 Binary files /dev/null and b/docs/images/find_tasks.PNG differ diff --git a/docs/images/list_tasks.PNG b/docs/images/list_tasks.PNG new file mode 100644 index 0000000000..0f8b415fc3 Binary files /dev/null and b/docs/images/list_tasks.PNG differ diff --git a/docs/images/save_tasks.PNG b/docs/images/save_tasks.PNG new file mode 100644 index 0000000000..bd1f109fe2 Binary files /dev/null and b/docs/images/save_tasks.PNG differ diff --git a/docs/images/snooze_tasks.PNG b/docs/images/snooze_tasks.PNG new file mode 100644 index 0000000000..a931c208c7 Binary files /dev/null and b/docs/images/snooze_tasks.PNG differ diff --git a/docs/images/tag_priority_tasks.PNG b/docs/images/tag_priority_tasks.PNG new file mode 100644 index 0000000000..a568c069cf Binary files /dev/null and b/docs/images/tag_priority_tasks.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..b3338b157c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Sep 05 23:32:25 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..b930e6f5be --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'dose' diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334cc..0000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} diff --git a/src/main/java/dose/Dose.java b/src/main/java/dose/Dose.java new file mode 100644 index 0000000000..6692cb4326 --- /dev/null +++ b/src/main/java/dose/Dose.java @@ -0,0 +1,24 @@ +package dose; + +import dose.util.cli.CliDose; + +public interface Dose { + + /** + * Runs Dose. + */ + void run(); + + /** + * Creates and runs a new instance of Dose. + * Invoked when Dose is run from the CLI. + * @param args Arguments supplied by the user. + */ + static void main(String[] args) { + // running Dose from the CLI should instantiate CliDose, as usual + Dose dose = new CliDose(); + dose.run(); + } + + void initializeStorage(); +} diff --git a/src/main/java/dose/Launcher.java b/src/main/java/dose/Launcher.java new file mode 100644 index 0000000000..806a8d1ad7 --- /dev/null +++ b/src/main/java/dose/Launcher.java @@ -0,0 +1,12 @@ +package dose; + +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/dose/Main.java b/src/main/java/dose/Main.java new file mode 100644 index 0000000000..526c39ca0f --- /dev/null +++ b/src/main/java/dose/Main.java @@ -0,0 +1,50 @@ +package dose; + +import dose.util.gui.GuiDose; +import dose.util.gui.MainWindow; +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + +/** + * A GUI for Dose using FXML. + */ +public class Main extends Application { + + /** Image to be used as application icon for Dose.*/ + private Image doseIcon = new Image(getClass().getResourceAsStream("/images/coffee.png")); + + /** Underlying instance of Dose created when application runs.*/ + private GuiDose dose = new GuiDose(); + + @Override + public void start(Stage stage) { + try { + // create new scene using MainWindow fxml + FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml")); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + + // set the scene to display on the stage + stage.setScene(scene); + + // set underlying instance of Dose + fxmlLoader.getController().setDose(dose); + + // set title and icon for the window + stage.setTitle("Dose"); + stage.getIcons().add(doseIcon); + + // display the window + stage.show(); + + // initialize Dose + dose.run(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/dose/command/AddDeadlineCommand.java b/src/main/java/dose/command/AddDeadlineCommand.java new file mode 100644 index 0000000000..587661e529 --- /dev/null +++ b/src/main/java/dose/command/AddDeadlineCommand.java @@ -0,0 +1,25 @@ +package dose.command; + +import dose.task.DeadlineTask; +import dose.task.Task; +import dose.util.exception.DoseException; + +/** + * Represents a command to create and add a DeadlineTask. + */ +public class AddDeadlineCommand extends AddTaskCommand { + public AddDeadlineCommand(String command) throws DoseException { + super(command); + s.useDelimiter("/by"); + setDescription(); + setDeadlineString(); + } + + @Override + public Task createTask() throws DoseException { + assert this.getDescription() != null; + assert this.getDeadlineString() != null; + + return new DeadlineTask(getDescription(), getDeadlineString()); + } +} diff --git a/src/main/java/dose/command/AddEventCommand.java b/src/main/java/dose/command/AddEventCommand.java new file mode 100644 index 0000000000..baf9517871 --- /dev/null +++ b/src/main/java/dose/command/AddEventCommand.java @@ -0,0 +1,25 @@ +package dose.command; + +import dose.util.exception.DoseException; +import dose.task.EventTask; +import dose.task.Task; + +/** + * Represents a command to create and add an Event task. + */ +public class AddEventCommand extends AddTaskCommand { + public AddEventCommand(String command) throws DoseException { + super(command); + s.useDelimiter("/at"); + setDescription(); + setDeadlineString(); + } + + @Override + public Task createTask() throws DoseException { + assert this.getDescription() != null; + assert this.getDeadlineString() != null; + + return new EventTask(getDescription(), getDeadlineString()); + } +} diff --git a/src/main/java/dose/command/AddTaskCommand.java b/src/main/java/dose/command/AddTaskCommand.java new file mode 100644 index 0000000000..63a948813c --- /dev/null +++ b/src/main/java/dose/command/AddTaskCommand.java @@ -0,0 +1,93 @@ +package dose.command; + +import dose.task.Task; +import dose.task.TaskList; +import dose.util.Storage; +import dose.util.Ui; +import dose.util.UiMessage; +import dose.util.exception.DoseException; +import dose.util.exception.ExceptionType; +import java.util.NoSuchElementException; +import java.util.Scanner; + +/** + * Represents a command to add a new task to the list of tasks. + */ +public abstract class AddTaskCommand implements Command { + Scanner s; + private String description; + private String deadlineString; + + AddTaskCommand(String fullCommand) { + super(); + this.s = new Scanner(fullCommand); + s.next(); // ignore command + } + + /** + * Returns the description of the task to be added. + * @return Description of the task to be added. + */ + String getDescription() { + return this.description; + } + + /** + * Returns the string representing the deadline of the task to be added. + * For Deadline tasks, this refers to the expected date of completion. + * For Event tasks, this refers to the expected date of occurrence. + * @return Deadline of the task to be added. + */ + String getDeadlineString() { + return this.deadlineString; + } + + /** + * Retrieves the description of the task to be added, based on command issued by user. + * @throws DoseException Application-specific exception thrown during execution. + */ + void setDescription() throws DoseException { + try { + this.description = this.s.next().strip(); + } catch (NoSuchElementException e) { + // user input after task type is blank + throw new DoseException(ExceptionType.DESCRIPTION_BLANK); + } + } + + /** + * Retrieves the string representing the deadline of the task to be added, + * based on command issued by user. + * @throws DoseException Application-specific exception thrown during execution. + */ + void setDeadlineString() throws DoseException { + try { + this.deadlineString = this.s.next().strip(); + } catch (NoSuchElementException e) { + // no deadline entered + throw new DoseException(ExceptionType.DEADLINE_BLANK); + } + } + + /** + * Creates the task to be added, based on command issued by the user. + * @return Task to be added. + */ + abstract Task createTask() throws DoseException; + + /** + * Creates a new Task based on command issued by the user, and adds it to the list of tasks. + * @param tasks List of tasks. + * @param ui UI to display to the user. + * @param storage Object that handles storage of task list to disk. + * @throws DoseException Application-specific exception thrown during execution. + */ + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DoseException { + Task newTask = createTask(); + tasks.add(newTask); + + String message = UiMessage.TASK_ADDED.getMessage() + " " + UiMessage.HINT_LIST.getMessage(); + ui.showMessage(message); + } +} diff --git a/src/main/java/dose/command/AddTodoCommand.java b/src/main/java/dose/command/AddTodoCommand.java new file mode 100644 index 0000000000..ca94525022 --- /dev/null +++ b/src/main/java/dose/command/AddTodoCommand.java @@ -0,0 +1,21 @@ +package dose.command; + +import dose.task.Task; +import dose.task.TodoTask; +import dose.util.exception.DoseException; + +/** + * Represents a command to create and add a TodoTask. + */ +public class AddTodoCommand extends AddTaskCommand { + public AddTodoCommand(String fullCommand) throws DoseException { + super(fullCommand); + s.useDelimiter("\n"); // no special delimiter required + setDescription(); + } + + @Override + public Task createTask() { + return new TodoTask(getDescription()); + } +} diff --git a/src/main/java/dose/command/Command.java b/src/main/java/dose/command/Command.java new file mode 100644 index 0000000000..cb599d99eb --- /dev/null +++ b/src/main/java/dose/command/Command.java @@ -0,0 +1,31 @@ +package dose.command; + +import dose.task.TaskList; +import dose.util.Storage; +import dose.util.Ui; +import dose.util.exception.DoseException; + +/** + * Represents commands requested by user and related operations, + * such as executing the command. + */ +public interface Command { + + /** + * Executes the command, by interacting with tasks and UI. + * @param tasks List of tasks. + * @param ui UI to display to the user. + * @param storage Object that handles storage of task list to disk. + * @throws DoseException Application-specific exception thrown during execution. + */ + void execute(TaskList tasks, Ui ui, Storage storage) throws DoseException; + // implementation varies for each subclass of Command + + /** + * Returns boolean indicating if command entered was "exit", false by default. + * @return boolean indicating if command entered was "exit". + */ + default boolean isExit() { + return false; + } +} diff --git a/src/main/java/dose/command/DeleteCommand.java b/src/main/java/dose/command/DeleteCommand.java new file mode 100644 index 0000000000..49d89a321b --- /dev/null +++ b/src/main/java/dose/command/DeleteCommand.java @@ -0,0 +1,35 @@ +package dose.command; + +import dose.task.Task; +import dose.task.TaskList; +import dose.util.Storage; +import dose.util.Ui; +import dose.util.UiMessage; +import dose.util.exception.DoseException; + +public class DeleteCommand extends ModifyTaskCommand { + + public DeleteCommand(String fullCommand) { + super(fullCommand); + } + + /** + * Deletes a task, based on command issued by the user. + * @param tasks List of tasks. + * @param ui UI to display to the user. + * @param storage Object that handles storage of task list to disk. + * @throws DoseException Application-specific exception thrown during execution. + */ + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DoseException { + Task task = getTaskById(tasks); + String taskDeletedMessage = UiMessage.TASK_DELETED.getMessage() + task.toString(); + + tasks.deleteTask(task); + + ui.showMessage(taskDeletedMessage); + String message = UiMessage.TASKS_STATUS_FRONT.getMessage() + + tasks.getSize() + UiMessage.TASKS_STATUS_BACK.getMessage(); + ui.showMessage(message); + } +} diff --git a/src/main/java/dose/command/DoneCommand.java b/src/main/java/dose/command/DoneCommand.java new file mode 100644 index 0000000000..5d4970fea7 --- /dev/null +++ b/src/main/java/dose/command/DoneCommand.java @@ -0,0 +1,34 @@ +package dose.command; + +import dose.task.Task; +import dose.task.TaskList; +import dose.util.Storage; +import dose.util.Ui; +import dose.util.UiMessage; +import dose.util.exception.DoseException; + +/** + * Represents a request from the user to mark a given task as done. + */ +public class DoneCommand extends ModifyTaskCommand { + + public DoneCommand(String fullCommand) { + super(fullCommand); + } + + /** + * Marks the task in the command issued by the user as done. + * @param tasks List of tasks. + * @param ui UI to display to the user. + * @param storage Object that handles storage of task list to disk. + * @throws DoseException Application-specific exception thrown during execution. + */ + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DoseException { + Task task = getTaskById(tasks); + task.markAsDone(); + + String message = UiMessage.TASK_DONE.getMessage() + task.toString(); + ui.showMessage(message); + } +} diff --git a/src/main/java/dose/command/ExitCommand.java b/src/main/java/dose/command/ExitCommand.java new file mode 100644 index 0000000000..560de6f5e1 --- /dev/null +++ b/src/main/java/dose/command/ExitCommand.java @@ -0,0 +1,35 @@ +package dose.command; + +import dose.task.TaskList; +import dose.util.Storage; +import dose.util.Ui; +import dose.util.UiMessage; + +/** + * Exits the application. + */ +public class ExitCommand implements Command { + + /** + * Exits the application. + * @param tasks List of tasks. + * @param ui UI to display to the user. + * @param storage Object that handles storage of task list to disk. + */ + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) { + // todo: in future, "lock down" ui and actually exit Dose when this command is called? + // todo: call this command when the "close" button is pressed by user + storage.save(tasks); + ui.showMessage(UiMessage.GOODBYE); + } + + /** + * Returns boolean indicating if command entered was "exit", true in this case. + * @return boolean indicating if command entered was "exit". + */ + @Override + public boolean isExit() { + return true; + } +} diff --git a/src/main/java/dose/command/FindCommand.java b/src/main/java/dose/command/FindCommand.java new file mode 100644 index 0000000000..f8a05b3302 --- /dev/null +++ b/src/main/java/dose/command/FindCommand.java @@ -0,0 +1,51 @@ +package dose.command; + +import dose.task.TaskList; +import dose.util.Storage; +import dose.util.Ui; +import dose.util.UiMessage; +import dose.util.exception.DoseException; +import dose.util.exception.ExceptionType; +import java.util.InputMismatchException; +import java.util.Scanner; + +public class FindCommand implements Command { + private Scanner s; + + /** + * Constructs a new FindCommand, given the full command issued by the user. + * @param fullCommand Full command issued by the user. + */ + public FindCommand(String fullCommand) { + super(); + this.s = new Scanner(fullCommand); + } + + /** + * Finds a list of tasks matching the keyword issued by the user. + * @param tasks List of tasks. + * @param ui UI to display to the user. + * @param storage Object that handles storage of task list to disk. + * @throws DoseException Application-specific exception thrown during execution. + */ + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DoseException { + try { + // first, try to get keyword + s.next(); // command (find), to be ignored + String keyword = s.nextLine(); + + // if ok, find and return matching tasks + TaskList matchingTasks = tasks.findTasks(keyword); + if (matchingTasks.isEmpty()) { + throw new DoseException(ExceptionType.NO_MATCHING_TASKS); + } else { + ui.showMessage(UiMessage.MATCHING_TASKS); + ui.showTasks(matchingTasks); + } + } catch (InputMismatchException e) { + // user input after "find" is empty + throw new DoseException(ExceptionType.KEYWORD_BLANK); + } + } +} diff --git a/src/main/java/dose/command/HelpCommand.java b/src/main/java/dose/command/HelpCommand.java new file mode 100644 index 0000000000..2d9a28153c --- /dev/null +++ b/src/main/java/dose/command/HelpCommand.java @@ -0,0 +1,20 @@ +package dose.command; + +import dose.task.TaskList; +import dose.util.Storage; +import dose.util.Ui; +import dose.util.UiMessage; + +public class HelpCommand implements Command { + + /** + * Shows a list of commands available in the application. + * @param tasks List of tasks. + * @param ui UI to display to the user. + * @param storage Object that handles storage of task list to disk. + */ + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) { + ui.showMessage(UiMessage.getHelpMessage()); + } +} diff --git a/src/main/java/dose/command/ListCommand.java b/src/main/java/dose/command/ListCommand.java new file mode 100644 index 0000000000..e76accfdec --- /dev/null +++ b/src/main/java/dose/command/ListCommand.java @@ -0,0 +1,27 @@ +package dose.command; + +import dose.task.TaskList; +import dose.util.Storage; +import dose.util.Ui; +import dose.util.UiMessage; +import dose.util.exception.DoseException; +import dose.util.exception.ExceptionType; + +public class ListCommand implements Command { + + /** + * Lists all tasks currently in the list. + * @param tasks List of tasks. + * @param ui UI to display to the user. + * @param storage Object that handles storage of task list to disk. + * @throws DoseException Application-specific exception thrown during execution. + */ + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DoseException { + if (tasks.isEmpty()) { + throw new DoseException(ExceptionType.TASK_LIST_EMPTY); + } + ui.showMessage(UiMessage.TASK_LIST); + ui.showTasks(tasks); + } +} diff --git a/src/main/java/dose/command/ModifyTaskCommand.java b/src/main/java/dose/command/ModifyTaskCommand.java new file mode 100644 index 0000000000..d5f4dd5764 --- /dev/null +++ b/src/main/java/dose/command/ModifyTaskCommand.java @@ -0,0 +1,38 @@ +package dose.command; + +import dose.task.Task; +import dose.task.TaskList; +import dose.util.exception.DoseException; +import dose.util.exception.ExceptionType; +import java.util.InputMismatchException; +import java.util.NoSuchElementException; +import java.util.Scanner; + +public abstract class ModifyTaskCommand implements Command{ + Scanner s; + + public ModifyTaskCommand(String fullCommand) { + super(); + s = new Scanner(fullCommand); + } + + public Task getTaskById(TaskList tasks) throws DoseException { + int taskId; + Task task; + + try { + // first, try to get taskId + s.next(); // command, to be ignored + taskId = s.nextInt(); + task = tasks.getTask(taskId); + + return task; + } catch (InputMismatchException | IndexOutOfBoundsException e) { + // user input after "tag" is not an int, or is an invalid task ID + throw new DoseException(ExceptionType.INVALID_ID); + } catch (NoSuchElementException e) { + // user input after "tag" is blank + throw new DoseException(ExceptionType.ID_BLANK); + } + } +} diff --git a/src/main/java/dose/command/PriorityCommand.java b/src/main/java/dose/command/PriorityCommand.java new file mode 100644 index 0000000000..3bd5d0c852 --- /dev/null +++ b/src/main/java/dose/command/PriorityCommand.java @@ -0,0 +1,51 @@ +package dose.command; + +import static dose.task.TaskPriority.getTaskPriority; + +import dose.task.Task; +import dose.task.TaskList; +import dose.task.TaskPriority; +import dose.util.Storage; +import dose.util.Ui; +import dose.util.UiMessage; +import dose.util.exception.DoseException; +import dose.util.exception.ExceptionType; +import java.util.NoSuchElementException; + +/** + * Represents a command to add a priority to a task. + */ +public class PriorityCommand extends ModifyTaskCommand { + public PriorityCommand(String fullCommand) { + super(fullCommand); + } + + /** + * Adds a priority to the task, based on command issued by the user. + * @param tasks List of tasks. + * @param ui UI to display to the user. + * @param storage Object that handles storage of task list to disk. + * @throws DoseException Application-specific exception thrown during execution. + */ + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DoseException { + Task task = getTaskById(tasks); + + // then, try to get priority + try { + String priorityString = s.next().strip(); + TaskPriority priority = getTaskPriority(priorityString); + + // if ok, add priority to task + task.addPriority(priority); + + // todo: handle invalid priority string (currently, it just removes priority since null) + + String message = UiMessage.TASK_PRIORITISED.getMessage() + task.toString(); + ui.showMessage(message); + } catch (NoSuchElementException e) { + // user input after taskId is blank + throw new DoseException(ExceptionType.TAG_BLANK); + } + } +} diff --git a/src/main/java/dose/command/SaveCommand.java b/src/main/java/dose/command/SaveCommand.java new file mode 100644 index 0000000000..025aa7973d --- /dev/null +++ b/src/main/java/dose/command/SaveCommand.java @@ -0,0 +1,20 @@ +package dose.command; + +import dose.task.TaskList; +import dose.util.Storage; +import dose.util.Ui; +import dose.util.exception.DoseException; + +public class SaveCommand implements Command { + + /** + * Saves the current status of the task list to the disk. + * @param tasks List of tasks. + * @param ui UI to display to the user. + * @param storage Object that handles storage of task list to disk. + */ + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DoseException { + storage.save(tasks); + } +} diff --git a/src/main/java/dose/command/SnoozeCommand.java b/src/main/java/dose/command/SnoozeCommand.java new file mode 100644 index 0000000000..cb41127aa6 --- /dev/null +++ b/src/main/java/dose/command/SnoozeCommand.java @@ -0,0 +1,32 @@ +package dose.command; + +import dose.task.Task; +import dose.task.TaskList; +import dose.util.Storage; +import dose.util.Ui; +import dose.util.UiMessage; +import dose.util.exception.DoseException; + +/** + * Represents a command to postpone ("snooze") a task. + */ +public class SnoozeCommand extends ModifyTaskCommand { + + public SnoozeCommand(String fullCommand) { + super(fullCommand); + } + + /** + * Snoozes the task in the command issued by the user. + * @param tasks List of tasks. + * @param ui UI to display to the user. + * @param storage Object that handles storage of task list to disk. + * @throws DoseException Application-specific exception thrown during execution. + */ + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DoseException { + Task task = getTaskById(tasks); + task.snooze(); + ui.showMessage(UiMessage.TASK_SNOOZED); + } +} diff --git a/src/main/java/dose/command/TagCommand.java b/src/main/java/dose/command/TagCommand.java new file mode 100644 index 0000000000..312301f1fa --- /dev/null +++ b/src/main/java/dose/command/TagCommand.java @@ -0,0 +1,44 @@ +package dose.command; + +import dose.task.Task; +import dose.task.TaskList; +import dose.util.Storage; +import dose.util.Ui; +import dose.util.UiMessage; +import dose.util.exception.DoseException; +import dose.util.exception.ExceptionType; +import java.util.NoSuchElementException; + +/** + * Represents a command to add a tag to a task. + */ +public class TagCommand extends ModifyTaskCommand { + public TagCommand(String fullCommand) { + super(fullCommand); + } + + /** + * Adds a tag to the task, based on command issued by the user. + * @param tasks List of tasks. + * @param ui UI to display to the user. + * @param storage Object that handles storage of task list to disk. + * @throws DoseException Application-specific exception thrown during execution. + */ + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws DoseException { + Task task = getTaskById(tasks); + + // then, try to get tag + try { + String tag = s.next().strip(); + + // if ok, add tag to task + task.addTag(tag); + String message = UiMessage.TASK_TAGGED.getMessage() + task.toString(); + ui.showMessage(message); + } catch (NoSuchElementException e) { + // user input after taskId is blank + throw new DoseException(ExceptionType.TAG_BLANK); + } + } +} diff --git a/src/main/java/dose/task/DeadlineTask.java b/src/main/java/dose/task/DeadlineTask.java new file mode 100644 index 0000000000..231aeae5a1 --- /dev/null +++ b/src/main/java/dose/task/DeadlineTask.java @@ -0,0 +1,42 @@ +package dose.task; + +import static dose.util.DateTime.parseDate; +import static dose.util.DateTime.snoozeDate; + +import dose.util.exception.DoseException; +import java.util.Date; + +/** + * Represents a Deadline, a type of Task that has an expected date of completion. + */ +public class DeadlineTask extends Task { + private Date by; + + /** + * Constructs a new Deadline, with the specified description and date of completion. + * @param description Description of the Deadline. + * @param byString String representing expected date of completion of the Deadline. + */ + public DeadlineTask(String description, String byString) throws DoseException { + super(description); + this.type = TaskType.DEADLINE; + + this.by = parseDate(byString); + } + + /** + * Returns the String representation of a Deadline for display purposes. + * Adds the deadline of the Deadline to the String representation + * provided by the Task class. + * @return String representation of a Deadline for display purposes. + */ + @Override + public String toString() { + return super.toString() + " (by: " + by + ")"; + } + + @Override + public void snooze() { + this.by = snoozeDate(this.by); + } +} diff --git a/src/main/java/dose/task/EventTask.java b/src/main/java/dose/task/EventTask.java new file mode 100644 index 0000000000..c7bb779262 --- /dev/null +++ b/src/main/java/dose/task/EventTask.java @@ -0,0 +1,42 @@ +package dose.task; + +import static dose.util.DateTime.parseDate; +import static dose.util.DateTime.snoozeDate; + +import dose.util.exception.DoseException; +import java.util.Date; + +/** + * Represents an Event, a type of Task that has an expected date of occurrence. + */ +public class EventTask extends Task { + private Date at; + + /** + * Constructs a new Event, with the specified description and date of occurrence. + * @param description Description of the Event. + * @param atString String representing expected date of occurrence of the Event. + */ + public EventTask(String description, String atString) throws DoseException { + super(description); + this.type = TaskType.EVENT; + + this.at = parseDate(atString); + } + + /** + * Returns the String representation of an Event for display purposes. + * Adds the date of occurrence of the Event to the String representation + * provided by the Task class. + * @return String representation of a Event for display purposes. + */ + @Override + public String toString() { + return super.toString() + " (at: " + at + ")"; + } + + @Override + public void snooze() { + this.at = snoozeDate(this.at); + } +} diff --git a/src/main/java/dose/task/Task.java b/src/main/java/dose/task/Task.java new file mode 100644 index 0000000000..eb304e8c74 --- /dev/null +++ b/src/main/java/dose/task/Task.java @@ -0,0 +1,98 @@ +package dose.task; + +import java.io.Serializable; + +/** + * Represents a task, the building block of a TaskList object. + */ +public abstract class Task implements Serializable { + private String description; + private Boolean isDone = false; + TaskType type; + private String tag; + private TaskPriority priority; + + /** + * Constructs a Task, with the specified description. + * @param description Description of the Task. + */ + public Task(String description) { + this.description = description; + } + + /** + * Returns the String representation of a Task for display purposes. + * @return String representation of a Task for display purposes. + */ + @Override + public String toString() { + String typeInitial = "[" + this.type.getTaskTypeInitial() + "] "; + String status = "[" + this.getStatusIcon() + "] "; + + assert this.description != null; + String toString = typeInitial + status + this.description; + + if (this.hasTag()) { + toString = toString + " (tag: " + this.getTag() + ")"; + } + + if (this.hasPriority()) { + toString = toString + " (priority: " + this.getPriority() + ")"; + } + return toString; + } + + /** + * Returns the status icon corresponding to the status of the task (done or not done). + * @return Status icon corresponding to the status of the task (done or not done). + */ + private String getStatusIcon() { + return (isDone ? "Y" : "N"); // return Y or N + } + + /** + * Marks the task as done. + */ + public void markAsDone() { + this.isDone = true; + } + + /** + * Postpones the deadline of the given task by 1 day if no other argument is specified. + */ + public abstract void snooze(); + + /** + * Adds a tag to the given task. + * Currently supports only one tag per task. + * To override existing tag, use tag command. + * @param tag Tag to be added to the given task. + */ + public void addTag(String tag) { + this.tag = tag; + } + + private String getTag() { + return this.tag; + } + + private boolean hasTag() { + return this.getTag() != null; + } + + /** + * Adds a priority to the given task. + * @param priority Priority to be added to the given task. + */ + public void addPriority(TaskPriority priority) { + this.priority = priority; + } + + private TaskPriority getPriority() { + return this.priority; + } + + private boolean hasPriority() { + return this.getPriority() != null; + } +} diff --git a/src/main/java/dose/task/TaskList.java b/src/main/java/dose/task/TaskList.java new file mode 100644 index 0000000000..bef0d3f755 --- /dev/null +++ b/src/main/java/dose/task/TaskList.java @@ -0,0 +1,120 @@ +package dose.task; + +import java.io.Serializable; +import java.util.ArrayList; + +/** + * Represents the list of tasks in the application. + */ +public class TaskList implements Serializable { + + /** ArrayList containing tasks in the TaskList. */ + private ArrayList tasks; + + /** + * Constructs a new TaskList containing the tasks in the list provided. + * @param tasks ArrayList containing the tasks to be included in the new TaskList. + */ + public TaskList(ArrayList tasks) { + this.tasks = tasks; + } + + /** + * Constructs a new, empty TaskList. + */ + public TaskList() { + tasks = new ArrayList<>(); + } + + /** + * Returns ArrayList containing the tasks in the TaskList. + * @return ArrayList containing the tasks in the TaskList. + */ + public ArrayList getTasks() { + assert this.tasks != null; + return this.tasks; + } + + /** + * Returns true if the TaskList does not contain any tasks. + * @return True if the TaskList does not contain any tasks. + */ + public boolean isEmpty() { + return tasks.isEmpty(); + } + + /** + * Adds a new task to the list. + * @param task New task to be added to the list. + */ + public void add(Task task) { + tasks.add(task); + assert !this.isEmpty(); + } + + /** + * Prints the list of tasks to the UI. + */ + public void printList() { + for (Task task : tasks) { + int i = getId(task); + String s = task.toString(); + System.out.println(i + ". " + s); + } + } + + /** + * Returns the ID of the given task, in the given TaskList. + * Helper function for printList() method. + * @param task Task whose ID is required. + * @return ID of the given task. + */ + public int getId(Task task) { + int id = this.tasks.indexOf(task) + 1; + // getId should only ever be called on tasks that are present in the list + assert id > 1; + return this.tasks.indexOf(task) + 1; + } + + /** + * Returns the task with the given ID in this TaskList. + * @param taskId ID of the task. + * @return Task with the given ID. + * @throws IndexOutOfBoundsException Exception thrown when the given ID is not present in + * the TaskList. + */ + public Task getTask(int taskId) throws IndexOutOfBoundsException { + return tasks.get(taskId - 1); + } + + public int getSize() { + return tasks.size(); + } + + /** + * Deletes the given task from the TaskList. + * @param taskToDelete Task to be deleted from the TaskList. + */ + public void deleteTask(Task taskToDelete) { + assert tasks.contains(taskToDelete); + tasks.remove(taskToDelete); + } + + /** + * Returns a TaskList containing tasks that match the given keyword. + * @param keyword Keyword to be used to find tasks. + * @return TaskList containing tasks that match the given keyword. + */ + public TaskList findTasks(String keyword) { + // is it possible to implement without creating temporary TaskList object? + // so that the numbering of the tasks based on the original list can be preserved + TaskList matchingTasks = new TaskList(); + for (Task task : tasks) { + if (task.toString().contains(keyword)) { + matchingTasks.add(task); + } + } + return matchingTasks; + } + +} diff --git a/src/main/java/dose/task/TaskPriority.java b/src/main/java/dose/task/TaskPriority.java new file mode 100644 index 0000000000..9a1127c7a0 --- /dev/null +++ b/src/main/java/dose/task/TaskPriority.java @@ -0,0 +1,25 @@ +package dose.task; + +public enum TaskPriority { + LOW, + MEDIUM, + HIGH; + + /** + * Returns the TaskPriority, given a String describing its priority. + * @param taskPriorityString String describing the priority. + * @return TaskPriority, based on String provided. + */ + public static TaskPriority getTaskPriority(String taskPriorityString) { + switch (taskPriorityString) { + case "low": + return TaskPriority.LOW; + case "medium": + return TaskPriority.MEDIUM; + case "high": + return TaskPriority.HIGH; + default: + return null; + } + } +} diff --git a/src/main/java/dose/task/TaskType.java b/src/main/java/dose/task/TaskType.java new file mode 100644 index 0000000000..bb43787467 --- /dev/null +++ b/src/main/java/dose/task/TaskType.java @@ -0,0 +1,55 @@ +package dose.task; + +/** + * Represents the possible types of Tasks. + */ +public enum TaskType { + DEADLINE("by", "D"), + EVENT("at", "E"), + TODO("\n", "T"), + INVALID(null, null); + + private final String delimiter; + private final String initial; + + TaskType(String delimiter, String initial) { + this.delimiter = delimiter; + this.initial = initial; + } + + /** + * Returns the delimiter used to separate the description of a Task from its additional details. + * Possible usage: in an alternative implementation of AddTaskCommand. + * @return Delimiter used to separate the description of a Task from its additional details. + */ + public String getDelimiter() { + return this.delimiter; + } + + /** + * Returns the TaskType of the task, given a String describing its type. + * @param taskTypeString String describing the type of the Task. + * @return TaskType of the task. + */ + public static TaskType getTaskType(String taskTypeString) { + switch (taskTypeString) { + case "todo": + return TaskType.TODO; + case "event": + return TaskType.EVENT; + case "deadline": + return TaskType.DEADLINE; + default: + return TaskType.INVALID; + } + } + + /** + * Returns the initial representing the TaskType. + * Possible usage: for icons representing the TaskType in UI. + * @return Initial representing the TaskType. + */ + public String getTaskTypeInitial() { + return this.initial; + } +} diff --git a/src/main/java/dose/task/TodoTask.java b/src/main/java/dose/task/TodoTask.java new file mode 100644 index 0000000000..9978b83b2c --- /dev/null +++ b/src/main/java/dose/task/TodoTask.java @@ -0,0 +1,17 @@ +package dose.task; + +public class TodoTask extends Task { + public TodoTask(String description) { + super(description); + this.type = TaskType.TODO; + } + + /** + * Snoozes ("postpones") the given task. + * Not applicable to TodoTasks. + */ + @Override + public void snooze() { + // do nothing + } +} diff --git a/src/main/java/dose/util/DateTime.java b/src/main/java/dose/util/DateTime.java new file mode 100644 index 0000000000..43e6e6fb04 --- /dev/null +++ b/src/main/java/dose/util/DateTime.java @@ -0,0 +1,30 @@ +package dose.util; + +import dose.util.exception.DoseException; +import dose.util.exception.ExceptionType; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +//import dose.util.Ui; + +public class DateTime { + private static SimpleDateFormat dateFormatter = new SimpleDateFormat("dd/MM/yyyy HHmm"); + + public static Date parseDate(String date) throws DoseException { + try { + return dateFormatter.parse(date); + } catch (ParseException e) { + // deadline entered in wrong format + throw new DoseException(ExceptionType.INVALID_DATE); + } + } + + public static Date snoozeDate(Date date) { + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.add(Calendar.DATE, 1); + date = c.getTime(); + return date; + } +} diff --git a/src/main/java/dose/util/Parser.java b/src/main/java/dose/util/Parser.java new file mode 100644 index 0000000000..ff7b57388e --- /dev/null +++ b/src/main/java/dose/util/Parser.java @@ -0,0 +1,44 @@ +package dose.util; + +import dose.command.*; +import dose.util.exception.DoseException; +import dose.util.exception.ExceptionType; +import java.util.Scanner; + +public class Parser { + + public static Command parse(String fullCommand) throws DoseException { + Scanner commandReader = new Scanner(fullCommand); + String command = commandReader.next(); + + if (command.equals("bye")) { + return new ExitCommand(); + } else if (command.equals("list")) { + return new ListCommand(); + } else if (command.equals("done")) { + return new DoneCommand(fullCommand); + } else if (command.equals("delete")) { + return new DeleteCommand(fullCommand); + } else if (command.equals("find")) { + return new FindCommand(fullCommand); + } else if (command.equals("todo")) { + return new AddTodoCommand(fullCommand); + } else if (command.equals("event")) { + return new AddEventCommand(fullCommand); + } else if (command.equals("deadline")) { + return new AddDeadlineCommand(fullCommand); + } else if (command.equals("snooze")) { + return new SnoozeCommand(fullCommand); + } else if (command.equals("tag")) { + return new TagCommand(fullCommand); + } else if (command.equals("priority")) { + return new PriorityCommand(fullCommand); + } else if (command.equals("save")) { + return new SaveCommand(); // disregard any input after "save" + } else if (command.equals("help")) { + return new HelpCommand(); // disregard any input after "help" (for now) + } else { + throw new DoseException(ExceptionType.INVALID_COMMAND); + } + } +} diff --git a/src/main/java/dose/util/Storage.java b/src/main/java/dose/util/Storage.java new file mode 100644 index 0000000000..b2271f6319 --- /dev/null +++ b/src/main/java/dose/util/Storage.java @@ -0,0 +1,90 @@ +package dose.util; + +import dose.task.TaskList; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +public class Storage { + private String filePath; + private Ui ui; + + public Storage(String filePath, Ui ui) { + this.filePath = filePath; + this.ui = ui; + } + + private boolean hasDirectory() { + // check if parent directory exists. if not, create it + File file = new File(this.filePath); + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + return false; + } else { + return true; + } + } + private boolean hasFile() { + File file = new File(this.filePath); + + // check if file exists + if (!file.exists()) { + ui.showMessage(UiMessage.TASKS_NOT_FOUND); + return false; + } + + return true; + } + + public TaskList load() { + TaskList tasks = new TaskList(); + // if directory does not exist, or file does not exist, no need to load from file + if (!hasDirectory() || !hasFile()) { + return tasks; + } + + try { + FileInputStream fis = new FileInputStream(filePath); + ObjectInputStream ois = new ObjectInputStream(fis); + + tasks = (TaskList) ois.readObject(); + + ois.close(); + fis.close(); + + String message = UiMessage.TASKS_IMPORTED.getMessage() + this.filePath; + ui.showMessage(message); + } catch (Exception e) { + // temporary haxx + e.printStackTrace(); + } + + return tasks; + } + + public void save(TaskList tasks) { + // first, check if task list is empty. if so, do not save + if (tasks.isEmpty()) { + ui.showMessage(UiMessage.TASKS_NOT_SAVED); + return; + } + + try { + FileOutputStream fos = new FileOutputStream(this.filePath); + ObjectOutputStream oos = new ObjectOutputStream(fos); + + oos.writeObject(tasks); + + oos.close(); + fos.close(); + + String message = UiMessage.TASKS_SAVED.getMessage() + this.filePath; + ui.showMessage(message); + } catch (Exception e) { + // temporary haxx + e.printStackTrace(); + } + } +} diff --git a/src/main/java/dose/util/Ui.java b/src/main/java/dose/util/Ui.java new file mode 100644 index 0000000000..2a6dde4b6d --- /dev/null +++ b/src/main/java/dose/util/Ui.java @@ -0,0 +1,39 @@ +package dose.util; + +import dose.task.TaskList; +import dose.util.exception.DoseException; + +/** + * Encapsulates methods related to displaying responses from Dose in the UI. + */ +public interface Ui { + /** + * Returns the command entered by the user. + * @return Command entered by the user. + */ + String readCommand(); + + /** + * Displays the required message in the UI, given the type of message required. + * @param uiMessage Enum indicating type of message required to be displayed. + */ + void showMessage(UiMessage uiMessage); + + /** + * Displays the required message in the UI, given the message required as a String. + * @param message String representing message to be displayed. + */ + void showMessage(String message); + + /** + * Displays the required error message in the UI, given the type of exception thrown. + * @param exception Exception thrown by the application. + */ + void showError(DoseException exception); + + /** + * Displays a list of tasks in the UI. + * @param tasks Tasks to be shown in the UI. + */ + void showTasks(TaskList tasks); +} diff --git a/src/main/java/dose/util/UiMessage.java b/src/main/java/dose/util/UiMessage.java new file mode 100644 index 0000000000..d943cb8d63 --- /dev/null +++ b/src/main/java/dose/util/UiMessage.java @@ -0,0 +1,64 @@ +package dose.util; + +/** + * Represents different types of events that need to be displayed to the user within the UI. + */ +public enum UiMessage { + // greetings + WELCOME("Hello! What can I do for you?"), + GOODBYE("Bye. Hope to see you again soon!"), + // storage + TASKS_IMPORTED("Success! Your tasks have been imported from: "), + TASKS_SAVED("Success! Your tasks have been saved to: "), + TASKS_NOT_SAVED("Your task list is empty! Adios :)"), + TASKS_NOT_FOUND("Existing tasks file not found! Starting dose afresh..."), + // commands + TASK_ADDED("Okay! I've added the task."), + TASK_DONE("Nice! I've marked this task as done: "), + TASK_SNOOZED("Okay! The task has been snoozed by 1 day."), + TASK_DELETED("Noted. I've removed this task: "), + MATCHING_TASKS("Here are the matching tasks in your list..."), + TASK_TAGGED("Nice! I've added a tag to this task: "), + TASK_PRIORITISED("Sweet! I've added a priority to this task: "), + // helpers + TASK_LIST("Here are the tasks in your list: "), + TASKS_STATUS_FRONT("Now you have "), + TASKS_STATUS_BACK(" items in this list."), + HINT_TODO("Use todo to add a new todo."), + HINT_DEADLINE("Use deadline /by [deadline] to add a new task with a deadline."), + HINT_EVENT("Use event /at [time] to add a new event at a time."), + HINT_DONE("Use done [taskId] to mark a task as done."), + HINT_DELETE("Use delete [taskId] to remove a task from the list."), + HINT_SAVE("Use save to save your tasks to disk."), + HINT_LIST("Use list to see all your tasks!"), + // todo: add tag, priority, find, snooze + HELP("Here are the things I can do..."); + + private final String message; + //private ArrayList helpMessages = new ArrayList<>(); + + UiMessage(String message) { + this.message = message; + } + + /** + * Returns a message relating to the event, to be displayed in the UI. + * @return Message relating to the event, to be displayed in the UI. + */ + public String getMessage() { + return this.message; + } + + public static String getHelpMessage() { + StringBuilder sb = new StringBuilder(); + sb.append(HELP.getMessage()).append("\n"); + sb.append(HINT_TODO.getMessage()).append("\n"); + sb.append(HINT_DEADLINE.getMessage()).append("\n"); + sb.append(HINT_EVENT.getMessage()).append("\n"); + sb.append(HINT_DONE.getMessage()).append("\n"); + sb.append(HINT_DELETE.getMessage()).append("\n"); + sb.append(HINT_SAVE.getMessage()).append("\n"); + sb.append(HINT_LIST.getMessage()).append("\n"); + return sb.toString(); + } +} diff --git a/src/main/java/dose/util/cli/Cli.java b/src/main/java/dose/util/cli/Cli.java new file mode 100644 index 0000000000..175f69260b --- /dev/null +++ b/src/main/java/dose/util/cli/Cli.java @@ -0,0 +1,52 @@ +package dose.util.cli; + +import dose.task.TaskList; +import dose.util.Ui; +import dose.util.UiMessage; +import dose.util.exception.DoseException; +import java.util.Scanner; + +/** + * Handles Dose's UI when in CLI mode. + */ +public class Cli implements Ui { + private Scanner in; + + public Cli() { + this.in = new Scanner(System.in); + } + + @Override + public String readCommand() { + return in.nextLine(); + } + + @Override + public void showMessage(UiMessage uiMessage) { + System.out.println(uiMessage.getMessage()); + } + + @Override + public void showMessage(String message) { + System.out.println(message); + } + + @Override + public void showError(DoseException exception) { + System.out.println(exception.getMessage()); + } + + @Override + public void showTasks(TaskList tasks) { + System.out.println("Here are the matching tasks in your list:"); + tasks.printList(); + } + + /** + * Displays a divider line in the UI. + */ + public void showLine() { + String line = "____________________________________________________________"; + System.out.println(line); + } +} diff --git a/src/main/java/dose/util/cli/CliDose.java b/src/main/java/dose/util/cli/CliDose.java new file mode 100644 index 0000000000..cb98a51910 --- /dev/null +++ b/src/main/java/dose/util/cli/CliDose.java @@ -0,0 +1,72 @@ +package dose.util.cli; + +import dose.Dose; +import dose.command.Command; +import dose.task.TaskList; +import dose.util.Parser; +import dose.util.Storage; +import dose.util.UiMessage; +import dose.util.exception.DoseException; + +public class CliDose implements Dose { + Storage storage; + TaskList tasks; + String filePath = "data/tasks.txt"; + + /** CLI implementation of Dose uses a Cli object to represent its UI. */ + Cli ui; + + /** + * Creates a new instance of Dose, with the default filePath. + */ + public CliDose() { + ui = new Cli(); + storage = new Storage(filePath, ui); + tasks = new TaskList(); + } + + /** + * Runs Dose from the CLI. All output is displayed in the CLI. + */ + public void run() { + ui.showMessage(UiMessage.WELCOME); + initializeStorage(); + + boolean isExit = false; + while (!isExit) { + try { + String fullCommand = ui.readCommand(); + ui.showLine(); // show the divider line ("_______") + Command c = Parser.parse(fullCommand); + c.execute(tasks, ui, storage); + isExit = c.isExit(); + } catch (DoseException e) { + ui.showError(e); + } finally { + ui.showLine(); + } + } + } + + /** + * Creates and runs a new instance of Dose from the CLI. + * @param args Arguments supplied by the user. + */ + public static void main(String[] args) { + CliDose duke = new CliDose(); + duke.run(); + } + + /** + * Attempts to import an existing task list. + */ + public void initializeStorage() { + try { + TaskList tasksFromFile = storage.load(); + tasks = tasksFromFile; + } catch (Exception e) { + // temporary haxx + e.printStackTrace(); + } + } +} diff --git a/src/main/java/dose/util/exception/DoseException.java b/src/main/java/dose/util/exception/DoseException.java new file mode 100644 index 0000000000..6561f3db16 --- /dev/null +++ b/src/main/java/dose/util/exception/DoseException.java @@ -0,0 +1,15 @@ +package dose.util.exception; + +public class DoseException extends Exception { + private ExceptionType exceptionType; + + public DoseException(ExceptionType exceptionType) { + super(); + this.exceptionType = exceptionType; + } + + @Override + public String getMessage() { + return this.exceptionType.getMessage(); + } +} diff --git a/src/main/java/dose/util/exception/ExceptionType.java b/src/main/java/dose/util/exception/ExceptionType.java new file mode 100644 index 0000000000..65e60068e4 --- /dev/null +++ b/src/main/java/dose/util/exception/ExceptionType.java @@ -0,0 +1,35 @@ +package dose.util.exception; + +/** + * Represents different types of Exceptions unique to the application. + */ +public enum ExceptionType { + // invalid + INVALID_ID("Oops! You entered an invalid task ID!"), + INVALID_COMMAND("Oops! I don't know what that means. Try using help to see what I can do."), + INVALID_DATE("Oops! You did not enter the date in an appropriate format.\n" + + "Try: DD/MM/YYYY HHmm instead."), + // blank + ID_BLANK("Oops! You did not enter a task ID!"), + DESCRIPTION_BLANK("Oops! You did not enter a description!"), + DEADLINE_BLANK("Oops! You did not enter a deadline!"), + KEYWORD_BLANK("Oops! You did not enter a keyword!"), + TAG_BLANK("Oops! You did not enter a tag!"), + // nothing to return + NO_MATCHING_TASKS("There are no tasks matching your query :("), + TASK_LIST_EMPTY("The task list is empty."); + + private final String message; + + ExceptionType(String message) { + this.message = message; + } + + /** + * Returns a message describing the Exception, to be displayed in the UI. + * @return Message describing the Exception, to be displayed in the UI. + */ + public String getMessage() { + return this.message; + } +} diff --git a/src/main/java/dose/util/gui/ColourScheme.java b/src/main/java/dose/util/gui/ColourScheme.java new file mode 100644 index 0000000000..5f514da9c4 --- /dev/null +++ b/src/main/java/dose/util/gui/ColourScheme.java @@ -0,0 +1,49 @@ +package dose.util.gui; + +public enum ColourScheme { + MINT("#b2eee6", "#8ad6cc", "#66beb2", + "#f99192", "#f97171", "#333333"), + GREY("#f4f6f9", "#e5e8ec", "#cbd0d8", + "#a9b1bc", "#646c77", "#424953"); + + private final String backgroundColour; + private final String dukeMessageBoxColour; + private final String dukeShadowColour; // also serves as the colour of the taskMessageBox + private final String userMessageBoxColour; + private final String userShadowColour; // also serves as the colour of the exceptionMessageBox + private final String textColour; + + public String getBackgroundColour() { + return backgroundColour; + } + + public String getDukeMessageBoxColour() { + return dukeMessageBoxColour; + } + + public String getDukeShadowColour() { + return dukeShadowColour; + } + + public String getUserMessageBoxColour() { + return userMessageBoxColour; + } + + public String getUserShadowColour() { + return userShadowColour; + } + + public String getTextColour() { + return textColour; + } + + ColourScheme(String backgroundColour, String dukeMessageBoxColour, String dukeShadowColour, + String userMessageBoxColour, String userShadowColour, String textColour) { + this.backgroundColour = backgroundColour; + this.dukeMessageBoxColour = dukeMessageBoxColour; + this.dukeShadowColour = dukeShadowColour; + this.userMessageBoxColour = userMessageBoxColour; + this.userShadowColour = userShadowColour; + this.textColour = textColour; + } +} diff --git a/src/main/java/dose/util/gui/Gui.java b/src/main/java/dose/util/gui/Gui.java new file mode 100644 index 0000000000..eaec064e16 --- /dev/null +++ b/src/main/java/dose/util/gui/Gui.java @@ -0,0 +1,92 @@ +package dose.util.gui; + +import dose.task.Task; +import dose.task.TaskList; +import dose.util.Ui; +import dose.util.UiMessage; +import dose.util.exception.DoseException; +import dose.util.gui.messagebox.DukeMessageBox; +import dose.util.gui.messagebox.ExceptionMessageBox; +import dose.util.gui.messagebox.MessageBox; +import dose.util.gui.messagebox.TaskMessageBox; +import dose.util.gui.messagebox.UserMessageBox; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Queue; + +/** + * Handles Dose's UI when in GUI mode. + */ +public class Gui implements Ui { + + /** Represents the colour scheme in use for the GUI. Hardcoded as MINT for now. */ + private ColourScheme colourScheme = ColourScheme.MINT; + + /** Represents the queue of messages to be displayed in the GUI. */ + // todo: change Queue to Stream + private Queue messageBoxQueue = new LinkedList<>(); + + /** + * Not in use for Dose's GUI. + * @return Nothing. + */ + @Override + public String readCommand() { + return null; + } + + public void showUserInput(String input) { + try { + messageBoxQueue.add(new UserMessageBox(input, this.colourScheme)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void showMessage(UiMessage uiMessage) { + try { + messageBoxQueue.add(new DukeMessageBox(uiMessage.getMessage(), this.colourScheme)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void showMessage(String message) { + try { + messageBoxQueue.add(new DukeMessageBox(message, this.colourScheme)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void showError(DoseException exception) { + try { + messageBoxQueue.add(new ExceptionMessageBox(exception.getMessage(), this.colourScheme)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void showTasks(TaskList tasks) { + ArrayList listOfTasks = tasks.getTasks(); + for (Task task : listOfTasks) { + showTask(task, tasks); + } + } + + public void showTask(Task task, TaskList tasks) { + String taskString = task.toString(); + int taskIdString = tasks.getId(task); + String toPrint = taskIdString + ". " + taskString; + messageBoxQueue.add(new TaskMessageBox(toPrint, this.colourScheme)); + } + + public Queue getMessageBoxQueue() { + return messageBoxQueue; + // todo: move clear() to here, makes more sense + } +} diff --git a/src/main/java/dose/util/gui/GuiDose.java b/src/main/java/dose/util/gui/GuiDose.java new file mode 100644 index 0000000000..9af13bf9c2 --- /dev/null +++ b/src/main/java/dose/util/gui/GuiDose.java @@ -0,0 +1,67 @@ +package dose.util.gui; + +import dose.Dose; +import dose.command.Command; +import dose.task.TaskList; +import dose.util.Parser; +import dose.util.Storage; +import dose.util.UiMessage; +import dose.util.exception.DoseException; + +public class GuiDose implements Dose { + Storage storage; + TaskList tasks; + String filePath = "/data/tasks.txt"; + + /** GUI implementation of Dose uses a Gui object to represent its UI. */ + Gui ui; + + /** + * Creates a new instance of Dose to be run from the GUI, with the default filePath. + */ + public GuiDose() { + ui = new Gui(); + storage = new Storage(filePath, ui); + tasks = new TaskList(); + } + + /** + * Get response from Dose to be displayed in the UI. + * May not be required after UI implementation is refined. + * @return Response from Dose to be displayed in the UI. + */ + public void getResponse(String input) { + try { + Command c = Parser.parse(input); + c.execute(tasks, ui, storage); + } catch (DoseException e) { + ui.showError(e); + } catch (Exception e) { + e.printStackTrace(); + } + + // dummy implementation +// return "Dose heard: " + input; + //return ui.getResponse(); + } + + @Override + public void run() { + // todo: figure out why welcome message doesn't appear until user's first input + ui.showMessage(UiMessage.WELCOME); + initializeStorage(); + } + + /** + * Attempts to import an existing task list. + */ + public void initializeStorage() { + try { + TaskList tasksFromFile = storage.load(); + tasks = tasksFromFile; + } catch (Exception e) { + // temporary haxx + e.printStackTrace(); + } + } +} diff --git a/src/main/java/dose/util/gui/MainWindow.java b/src/main/java/dose/util/gui/MainWindow.java new file mode 100644 index 0000000000..0b393847e6 --- /dev/null +++ b/src/main/java/dose/util/gui/MainWindow.java @@ -0,0 +1,77 @@ +package dose.util.gui; + +import dose.util.gui.messagebox.MessageBox; +import java.util.Queue; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; + +/** + * Controller for MainWindow. Provides the layout for the other controls. + */ +public class MainWindow extends AnchorPane { + @FXML + private ScrollPane scrollPane; + @FXML + private VBox messageBoxContainer; + @FXML + private TextField userInput; + @FXML + private Button sendButton; + + private GuiDose dose; + + /** Represents the colour scheme in use for the GUI. Hardcoded as MINT for now. */ + private ColourScheme colourScheme = ColourScheme.MINT; + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(messageBoxContainer.heightProperty()); + messageBoxContainer.setStyle("-fx-background-color: " + colourScheme.getBackgroundColour()); + } + + public void setDose(GuiDose d) { + dose = d; + } + + /** + * Creates two dialog boxes, one echoing user input and the other containing Dose's reply and then appends them to + * the dialog container. Clears the user input after processing. + */ + @FXML + private void handleUserInput() { + try { + // get and display user input + String input = userInput.getText(); + dose.ui.showUserInput(input); + + dose.getResponse(input); + + // clear user input field + userInput.clear(); + } catch (Exception e) { + e.printStackTrace(); + } + + try { + // get and display output from Dose + Queue messageBoxQueue = dose.ui.getMessageBoxQueue(); + messageBoxContainer.getChildren().addAll(messageBoxQueue); + dose.ui.getMessageBoxQueue().clear(); + } catch (Exception e) { + e.printStackTrace(); + } + } + +// /** +// * Changes the appearance of Dose's GUI to match the given colour scheme. +// * Not sure why this needs to be static. KIV implementation :( +// * @param colourScheme Colour scheme for Dose's GUI. +// */ +// public static void setColourScheme(ColourScheme colourScheme) { +// this.colourScheme = colourScheme; +// } +} \ No newline at end of file diff --git a/src/main/java/dose/util/gui/messagebox/DukeMessageBox.java b/src/main/java/dose/util/gui/messagebox/DukeMessageBox.java new file mode 100644 index 0000000000..50d7ca5346 --- /dev/null +++ b/src/main/java/dose/util/gui/messagebox/DukeMessageBox.java @@ -0,0 +1,15 @@ +package dose.util.gui.messagebox; + +import dose.util.gui.ColourScheme; +import javafx.scene.effect.DropShadow; +import javafx.scene.paint.Color; + +public class DukeMessageBox extends MessageBox { + public DukeMessageBox(String text, ColourScheme colourScheme) { + super(text); + messageBox.setStyle("-fx-background-color: " + colourScheme.getDukeMessageBoxColour()); + getMessage().setStyle("-fx-fill: " + colourScheme.getTextColour()); + DropShadow dropShadow = new DropShadow(5.0, Color.web(colourScheme.getDukeShadowColour())); + messageBox.setEffect(dropShadow); + } +} diff --git a/src/main/java/dose/util/gui/messagebox/ExceptionMessageBox.java b/src/main/java/dose/util/gui/messagebox/ExceptionMessageBox.java new file mode 100644 index 0000000000..80881bff69 --- /dev/null +++ b/src/main/java/dose/util/gui/messagebox/ExceptionMessageBox.java @@ -0,0 +1,16 @@ +package dose.util.gui.messagebox; + +import dose.util.gui.ColourScheme; +import javafx.scene.effect.DropShadow; +import javafx.scene.paint.Color; + +// todo: custom styling for exceptions, including exclamation mark etc +public class ExceptionMessageBox extends MessageBox { + public ExceptionMessageBox(String text, ColourScheme colourScheme) { + super(text); + messageBox.setStyle("-fx-background-color: " + colourScheme.getUserShadowColour()); + getMessage().setStyle("-fx-fill: " + colourScheme.getTextColour()); + DropShadow dropShadow = new DropShadow(5.0, Color.web(colourScheme.getUserShadowColour())); + messageBox.setEffect(dropShadow); + } +} diff --git a/src/main/java/dose/util/gui/messagebox/MessageBox.java b/src/main/java/dose/util/gui/messagebox/MessageBox.java new file mode 100644 index 0000000000..c1e1a3670e --- /dev/null +++ b/src/main/java/dose/util/gui/messagebox/MessageBox.java @@ -0,0 +1,53 @@ +package dose.util.gui.messagebox; + +import dose.util.gui.MainWindow; +import java.io.IOException; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +/** + * An example of a custom control using FXML. + * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label + * containing text from the speaker. + */ +// todo: replace Label with TextFlow to enable text to wrap multiple lines! +// todo: still can't get user input to align right :( +public abstract class MessageBox extends VBox { + + @FXML + private Label message; + + @FXML + HBox messageBox; + + @FXML + VBox messageBoxContainer; + + MessageBox(String text) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource( + "/view/MessageBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + // I give up :( + messageBoxContainer.setAlignment(Pos.TOP_LEFT); + message.setText(text); + } + + public Label getMessage() { + return message; + } + + public void setMessage(Label message) { + this.message = message; + } +} diff --git a/src/main/java/dose/util/gui/messagebox/TaskMessageBox.java b/src/main/java/dose/util/gui/messagebox/TaskMessageBox.java new file mode 100644 index 0000000000..37f4d6e9d3 --- /dev/null +++ b/src/main/java/dose/util/gui/messagebox/TaskMessageBox.java @@ -0,0 +1,16 @@ +package dose.util.gui.messagebox; + +import dose.util.gui.ColourScheme; +import javafx.scene.effect.DropShadow; +import javafx.scene.paint.Color; + +// todo: custom styling for tasks, including status, priority, deadline, tags etc +public class TaskMessageBox extends MessageBox { + public TaskMessageBox(String text, ColourScheme colourScheme) { + super(text); + messageBox.setStyle("-fx-background-color: " + colourScheme.getDukeShadowColour()); + getMessage().setStyle("-fx-fill: " + colourScheme.getTextColour()); + DropShadow dropShadow = new DropShadow(5.0, Color.web(colourScheme.getDukeShadowColour())); + messageBox.setEffect(dropShadow); + } +} diff --git a/src/main/java/dose/util/gui/messagebox/UserMessageBox.java b/src/main/java/dose/util/gui/messagebox/UserMessageBox.java new file mode 100644 index 0000000000..1d5a5929f4 --- /dev/null +++ b/src/main/java/dose/util/gui/messagebox/UserMessageBox.java @@ -0,0 +1,28 @@ +package dose.util.gui.messagebox; + +import dose.util.gui.ColourScheme; +import javafx.geometry.Pos; +import javafx.scene.effect.DropShadow; +import javafx.scene.paint.Color; + +public class UserMessageBox extends MessageBox { + public UserMessageBox(String text, ColourScheme colourScheme) { + super(text); + messageBox.setStyle("-fx-background-color: " + colourScheme.getUserMessageBoxColour()); + getMessage().setStyle("-fx-fill: " + colourScheme.getTextColour()); + DropShadow dropShadow = new DropShadow(5.0, Color.web(colourScheme.getUserShadowColour())); + messageBox.setEffect(dropShadow); + //flip(); + messageBoxContainer.setAlignment(Pos.TOP_RIGHT); + } + +// /** +// * 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_RIGHT); +// } +} diff --git a/src/main/resources/images/coffee.png b/src/main/resources/images/coffee.png new file mode 100644 index 0000000000..14f2a825b2 Binary files /dev/null and b/src/main/resources/images/coffee.png differ diff --git a/src/main/resources/images/kawaii_robot.png b/src/main/resources/images/kawaii_robot.png new file mode 100644 index 0000000000..74d6c83e3c Binary files /dev/null and b/src/main/resources/images/kawaii_robot.png differ diff --git a/src/main/resources/images/kawaii_robot_power.png b/src/main/resources/images/kawaii_robot_power.png new file mode 100644 index 0000000000..48cb73aaf8 Binary files /dev/null and b/src/main/resources/images/kawaii_robot_power.png differ diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..df697b3483 --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MessageBox.fxml b/src/main/resources/view/MessageBox.fxml new file mode 100644 index 0000000000..d37b71cd21 --- /dev/null +++ b/src/main/resources/view/MessageBox.fxml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/src/test/java/TaskTest.java b/src/test/java/TaskTest.java new file mode 100644 index 0000000000..d755939612 --- /dev/null +++ b/src/test/java/TaskTest.java @@ -0,0 +1,32 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; + +import dose.task.DeadlineTask; +import dose.task.EventTask; +import dose.task.Task; +import dose.task.TodoTask; +import dose.util.exception.DoseException; +import org.junit.jupiter.api.Test; + +public class TaskTest { + @Test + public void testTodoToString() { + Task task = new TodoTask("practice math"); + assertEquals("[T] [N] practice math", + task.toString()); + } + + @Test + public void testDeadlineToString() throws DoseException { + Task task = new DeadlineTask("this project", "30/9/2019 2300"); + assertEquals("[D] [N] this project (by: Mon Sep 30 23:00:00 SGT 2019)", + task.toString()); + } + + @Test + public void testEventToString() throws DoseException { + Task task = new EventTask("play guitar with friends", "1/10/2019 1900"); + assertEquals("[E] [N] play guitar with friends (at: Tue Oct 01 19:00:00 SGT 2019)", + task.toString()); + } + +} diff --git a/src/test/java/UiMessageTest.java b/src/test/java/UiMessageTest.java new file mode 100644 index 0000000000..ba570da8d8 --- /dev/null +++ b/src/test/java/UiMessageTest.java @@ -0,0 +1,37 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; + +import dose.util.UiMessage; +import org.junit.jupiter.api.Test; + +public class UiMessageTest { + @Test + public void testWelcome() { + UiMessage uiMessage = UiMessage.WELCOME; + assertEquals("Hello! What can I do for you?", + uiMessage.getMessage()); + } + + @Test + public void testTaskStatus() { + String uiMessageFront = UiMessage.TASKS_STATUS_FRONT.getMessage(); + int numTasks = 5; + String uiMessageBack = UiMessage.TASKS_STATUS_BACK.getMessage(); + String message = uiMessageFront + numTasks + uiMessageBack; + assertEquals("Now you have 5 items in this list.", + message); + } + + @Test + public void testHelpMessage() { + String helpMessage = UiMessage.getHelpMessage(); + assertEquals("Here are the things I can do...\n" + + "Use todo to add a new todo.\n" + + "Use deadline /by [deadline] to add a new task with a deadline.\n" + + "Use event /at [time] to add a new event at a time.\n" + + "Use done [taskId] to mark a task as done.\n" + + "Use delete [taskId] to remove a task from the list.\n" + + "Use save to save your tasks to disk.\n" + + "Use list to see all your tasks!\n", + helpMessage); + } +} diff --git a/tutorials/gradleTutorial.md b/tutorials/gradleTutorial.md index 08292b118d..c8dce655b2 100644 --- a/tutorials/gradleTutorial.md +++ b/tutorials/gradleTutorial.md @@ -30,10 +30,10 @@ As a developer, you write a _build file_ that describes the project. A build fil git checkout master git merge gradle ``` -1. Open the `build.gradle` file in an editor. Update the following code block to point to the main class (i.e., the one containing the `main` method) of your application. The code below assumes your main class is `seedu.duke.Duke` +1. Open the `build.gradle` file in an editor. Update the following code block to point to the main class (i.e., the one containing the `main` method) of your application. The code below assumes your main class is `seedu.dose.Dose` ```groovy application { - mainClassName = "seedu.duke.Duke" + mainClassName = "seedu.dose.Dose" } ``` 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. @@ -106,7 +106,7 @@ The plugin can be configured by setting some properties. Let's try to produce a Add the following block to your build file: ```groovy shadowJar { - archiveBaseName = "duke" + archiveBaseName = "dose" archiveVersion = "0.1.3" archiveClassifier = null archiveAppendix = null @@ -146,15 +146,15 @@ By convention, java tests belong in `src/test/java` folder. Create a new `test/j src ├─main │ └─java -│ └─seedu/duke/Duke.java +│ └─seedu/dose/Duke.java └─test └─java - └─seedu/duke/DukeTest.java + └─seedu/dose/DoseTest.java ``` If you have imported your Gradle project into IntelliJ IDEA, you will notice that IDEA is able to mark the test directory as the _Test root_ (colored in green by default) automatically. -You can now write a test (e.g., `test/java/seedu/duke/DukeTest.java`) and run it with `gradlew test`. +You can now write a test (e.g., `test/java/seedu/dose/DoseTest.java`) and run it with `gradlew test`. **Resources**: * [Gradle documentation for JUnit](https://docs.gradle.org/current/userguide/java_testing.html#using_junit5) diff --git a/tutorials/javaFxTutorialPart3.md b/tutorials/javaFxTutorialPart3.md index a9e1bdddd3..9ae617a5a6 100644 --- a/tutorials/javaFxTutorialPart3.md +++ b/tutorials/javaFxTutorialPart3.md @@ -115,7 +115,7 @@ Image|Filename public class Duke extends Application { // ... private Image user = new Image(this.getClass().getResourceAsStream("/images/DaUser.png")); - private Image duke = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png")); + private Image dose = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png")); // ... } ``` @@ -132,7 +132,7 @@ private void handleUserInput() { Label dukeText = new Label(getResponse(userInput.getText())); dialogContainer.getChildren().addAll( new DialogBox(userText, new ImageView(user)), - new DialogBox(dukeText, new ImageView(duke)) + new DialogBox(dukeText, new ImageView(dose)) ); userInput.clear(); } @@ -213,7 +213,7 @@ private void handleUserInput() { Label dukeText = new Label(getResponse(userInput.getText())); dialogContainer.getChildren().addAll( DialogBox.getUserDialog(userText, new ImageView(user)), - DialogBox.getDukeDialog(dukeText, new ImageView(duke)) + DialogBox.getDukeDialog(dukeText, new ImageView(dose)) ); userInput.clear(); } diff --git a/tutorials/javaFxTutorialPart4.md b/tutorials/javaFxTutorialPart4.md index 1a6e5bc412..a6ba0ba31a 100644 --- a/tutorials/javaFxTutorialPart4.md +++ b/tutorials/javaFxTutorialPart4.md @@ -128,7 +128,7 @@ public class MainWindow extends AnchorPane { @FXML private Button sendButton; - private Duke duke; + private Duke dose; private Image userImage = new Image(this.getClass().getResourceAsStream("/images/DaUser.png")); private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png")); @@ -139,7 +139,7 @@ public class MainWindow extends AnchorPane { } public void setDuke(Duke d) { - duke = d; + dose = d; } /** @@ -149,7 +149,7 @@ public class MainWindow extends AnchorPane { @FXML private void handleUserInput() { String input = userInput.getText(); - String response = duke.getResponse(input); + String response = dose.getResponse(input); dialogContainer.getChildren().addAll( DialogBox.getUserDialog(input, userImage), DialogBox.getDukeDialog(response, dukeImage) @@ -186,7 +186,7 @@ import javafx.stage.Stage; */ public class Main extends Application { - private Duke duke = new Duke(); + private Duke dose = new Duke(); @Override public void start(Stage stage) { @@ -195,7 +195,7 @@ public class Main extends Application { AnchorPane ap = fxmlLoader.load(); Scene scene = new Scene(ap); stage.setScene(scene); - fxmlLoader.getController().setDuke(duke); + fxmlLoader.getController().setDuke(dose); stage.show(); } catch (IOException e) { e.printStackTrace();