diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000000..2bc746d19a --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,35 @@ +name: Java CI + +on: [push, pull_request] + +jobs: + build: + strategy: + matrix: + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + + steps: + - name: Set up repository + uses: actions/checkout@master + + - name: Set up repository + uses: actions/checkout@master + with: + ref: master + + - name: Merge to master + run: git checkout --progress --force ${{ github.sha }} + + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v1 + + - name: Setup JDK 11 + uses: actions/setup-java@v1 + with: + java-version: '11' + java-package: jdk+fx + + - name: Build and check with Gradle + run: ./gradlew check + diff --git a/README.md b/README.md index 8715d4d915..ced141d216 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,42 @@ -# Duke project template - -This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. - -## Setting up in Intellij - -Prerequisites: JDK 11, update Intellij to the most recent version. - -1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project first) -1. Open the project into Intellij as follows: - 1. Click `Open`. - 1. Select the project directory, and click `OK`. - 1. If there are any further prompts, accept the defaults. -1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
- In the same dialog, set the **Project language level** field to the `SDK default` option. -3. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: - ``` - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - ``` +# :deer: Chopper helpdesk +> “Your mind is for having ideas, not holding them.” – David Allen ([source](https://dansilvestre.com/productivity-quotes/)) + +Chopper helpdesk frees your mind by remembering things you need to do + +It is +- Chat-based +- Has a user-friendly interface +- easy to learn +- ~~Fast~~ _SUPER FAST_ to use + +## :footprints: Steps to run +1. Download the JAR file from the latest release [here](https://github.com/anchengyang/ip/releases) +2. Launch the app in the terminal using the command `java -jar chopperHelpdesk.jar` +3. Start adding your tasks +4. Let it manage your tasks for you 👍 + +## :wrench: How to use +| No. | Feature | Command | +|-----|----------------------------------------|---------------------------------------------------------------------------------------------| +| 1. | Create todo items | `todo ` | +| 2. | Create deadlines | `deadline /by ` | +| 3. | Create events | `event /from /to ` | +| 4. | Filter by keyword | `find ` | +| 5. | Filter by date | `finddate ` | +| 6. | Mark items as completed | `mark ` | +| 7. | Unmark items | `unmark ` | +| 8. | Delete items | `delete ` | +| 9. | Update description of existing tasks | `update /description ` | +| 10. | Update deadlines of existing deadlines | `update /deadline ` | +| 11. | Quits the application | `bye` | + + +## :iphone: Screenshots +This is a screenshot of the application on start: + + + +This is a screenshot of the chat: + + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..c3024a8df7 --- /dev/null +++ b/build.gradle @@ -0,0 +1,63 @@ +plugins { + id 'checkstyle' + id 'java' + id 'application' + id 'com.github.johnrengelman.shadow' version '5.1.0' +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0' + + String javaFxVersion = '11' + + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + +application { + mainClassName = "duke.Launcher" +} + +shadowJar { + archiveBaseName = "chopperHelpdesk" + archiveClassifier = null +} + +run { + standardInput = System.in + enableAssertions = true +} + +checkstyle { + toolVersion = '10.2' +} + diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..e8ee76467b --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,429 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..135ea49ee0 --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 8077118ebe..36f14425b3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,177 @@ -# User Guide +# 🦌 Chopper Helpdesk User Guide -## Features +> “Your mind is for having ideas, not holding them.” – David Allen ([source](https://dansilvestre.com/productivity-quotes/)) -### Feature-ABC +Chopper helpdesk frees your mind by remembering things you need to do. It is a desktop chat-based app with a user-friendly interface that is easy to learn and ~~Fast~~ _SUPER FAST_ to use. -Description of the feature. +- [Steps to run](#-steps-to-run) +- [Features](#%EF%B8%8F-features) +- [Usage](#-usage) + - [Todo command](#1-create-todo-items-todo) + - [Deadline command](#2-create-deadlines-deadline) + - [Event command](#3-create-events-event) + - [Find command](#4-filter-by-keyword-find) + - [Finddate command](#5-filter-by-date-finddate) + - [Mark command](#6-mark-items-as-completed--mark) + - [Unmark command](#7-unmark-items-as-uncompleted--unmark) + - [Delete command](#8-delete-items-delete) + - [Update description command](#9-update-the-description-of-existing-tasks-update) + - [Update deadline command](#10-update-the-deadline-of-existing-deadlines-update) + - [Exit application](#11-quits-the-application-on-command-bye) +- [Command summary](#-command-summary) +- [Screenshots of app](#-screenshots) -### Feature-XYZ +## 🐾 Steps to run +1. Ensure you have `Java 11` installed in your device +2. Download the JAR file from the latest release [here](https://github.com/anchengyang/ip/releases) +3. Launch the app from the terminal using this command `java -jar chopperHelpdesk.jar` +4. Start adding your tasks using the commands [here](#usage) +5. Let it manage your tasks for you 👍 -Description of the feature. +## 🛠️ Features -## Usage +### Manages your tasks for you -### `Keyword` - Describe action +Add or delete tasks from the todo list using the index. -Describe the action and its outcome. +### Filter tasks -Example of usage: +You can easily locate the task you want based on specific keywords or dates. -`keyword (optional arguments)` +### Update existing tasks -Expected outcome: +Easily update the description or deadline of existing tasks without the hassle of deleting and creating new tasks. -Description of the outcome. +### Track completed and uncompleted tasks -``` -expected output -``` +Mark or unmark tasks that you have completed. + + +## 🌱 Usage + +### 1. Create todo items: `todo` + +Create todo items with the given description + +```` +[T] [ ] My todo item +```` + +Format: `todo ` + +Example of usage: `todo clean room` + +### 2. Create deadlines: `deadline` + +Create deadline items with the given description and deadline + +HHmm (hour minute in 24 hour time format) is optional to include in the deadline + +```` +[D] [ ] My deadline item (by: Oct 14 2023 04:30 PM) +```` + +Format: `deadline /by ` + +Example of usage: `deadline Apply for university /by 2023-04-01 2359>` + +### 3. Create events: `event` + +Create events with the given description and start and end dates + +HHmm (hour minute in 24 hour time format) is optional to include in the deadline + +```` +[E] [ ] My event item (from: Oct 14 2023 04:30 PM to: Oct 19 2023 05:00 PM) +```` + +Format: `event /from /to ` + +Example of usage: `event NUS open house /from 2024-03-04 /to 2024-03-04 1900>` + +### 4. Filter by Keyword: `find` + +Finds items in the list with descriptions that match the given keyword(s) + +Format: `find ` + +Example of usage: `find op` + +### 5. Filter by Date: `finddate` + +Finds items in the list with dates that match the given date + +Format: `find ` + +Example of usage: `find Oct` + +### 6. Mark items as completed: `mark` + +Marks items in the list as completed + +Example of usage: `mark ` + +Format: `mark 2` + +### 7. Unmark items as uncompleted: `unmark` + +Unmarks items in the list as uncompleted + +Example of usage: `unmark ` + +Format: `unmark 2` + +### 8. Delete items: `delete` + +Deletes items in the list + +Format: `delete ` + +Example of usage: `delete 1` + +### 9. Update the description of existing tasks: `update` + +Updates the description of existing tasks in the list without having to create a new task + +Format: `update /description ` + +Example of usage: `update 2 /description new homework` + +### 10. Update the deadline of existing deadlines: `update` + +Updates the deadline of existing deadline tasks in the list without having to create a new deadline + +Format: `update /deadline ` + +Example of usage: `update 3 /deadline 2023-09-10` + +### 11. Quits the application on command: `bye` + +Quits the application on command without having to click on the cross button of the window + +Format: `bye` + +## 📋 Command summary + +| **No.** | **Feature** | **Command** | +|---------|----------------------------------------|---------------------------------------------------------------------------------------------| +| 1. | Create todo items | `todo ` | +| 2. | Create deadlines | `deadline /by ` | +| 3. | Create events | `event /from /to ` | +| 4. | Filter by keyword | `find ` | +| 5. | Filter by date | `finddate ` | +| 6. | Mark items as completed | `mark ` | +| 7. | Unmark items | `unmark ` | +| 8. | Delete items | `delete ` | +| 9. | Update description of existing tasks | `update /description ` | +| 10. | Update deadlines of existing deadlines | `update /deadline ` | +| 11. | Quits the application | `bye` | + +## 📱 Screenshots +This is a screenshot of the application on start: + + + +This is a screenshot of the chat: + + diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..d9459a6972 Binary files /dev/null and b/docs/Ui.png differ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..f3d88b1c2f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..b7c8c5dbf5 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..2fe81a7d95 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..62bd9b9cce --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,103 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334cc..0000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} diff --git a/src/main/java/duke/DialogBox.java b/src/main/java/duke/DialogBox.java new file mode 100644 index 0000000000..f5b54bcd66 --- /dev/null +++ b/src/main/java/duke/DialogBox.java @@ -0,0 +1,75 @@ +package duke; +import java.io.IOException; +import java.util.Collections; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.shape.Circle; + +/** + * An example of a custom control using FXML. + * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label + * containing text from the speaker. + */ +public class DialogBox extends HBox { + @FXML + private Label dialog; + @FXML + private ImageView displayPicture; + + @FXML + private Circle circle = new Circle(50, 35, 30); + + private DialogBox(String text, Image img) { + try { + + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + dialog.setText(text); + displayPicture.setImage(img); + displayPicture.setClip(circle); + + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + Collections.reverse(tmp); + getChildren().setAll(tmp); + setAlignment(Pos.TOP_LEFT); + } + + public static DialogBox getUserDialog(String text, Image img) { + var db = new DialogBox(text, img); + + db.dialog.styleProperty().set("-fx-background-color: #708090"); + return db; + } + + public static DialogBox getDukeDialog(String text, Image img, boolean isWrong) { + var db = new DialogBox(text, img); + db.flip(); + if (isWrong) { + db.dialog.styleProperty().set("-fx-background-color: #a91b0d"); + } else { + db.dialog.styleProperty().set("-fx-background-color: #3b444b"); + } + return db; + } +} diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java new file mode 100644 index 0000000000..6a602031d1 --- /dev/null +++ b/src/main/java/duke/Duke.java @@ -0,0 +1,54 @@ +package duke; + +import duke.commands.Command; +import javafx.util.Pair; + +/** + * The main class. + */ +public class Duke { + + private Storage storage; + private TaskList tasks; + + /** + * Duke constructor. + */ + public Duke() { + String filePath = "tasks.txt"; + this.storage = new Storage(filePath); + try { + this.tasks = storage.loadTaskList(); + } catch (Exception e) { + System.out.println(Ui.showLoadingError()); + this.tasks = new TaskList(); + } + } + + /** + * Shows welcome message on start. + * + * @return String The welcome message. + */ + public String showWelcomeMessage() { + return Ui.showWelcomeMessage(); + } + + + /** + * Generates a response based on user input. + * + * @param input The user input. + * @return String of the response. + */ + public Pair getResponse(String input) { + try { + Command c = Parser.parse(input.trim()); + return new Pair<>(c.execute(this.tasks, this.storage), false); + } catch (IllegalArgumentException e) { + return new Pair<>(Ui.unrecognisedCommand(), true); + } catch (Exception e) { + return new Pair<>(e.getMessage(), true); + } + } +} diff --git a/src/main/java/duke/DukeException.java b/src/main/java/duke/DukeException.java new file mode 100644 index 0000000000..5353933aa6 --- /dev/null +++ b/src/main/java/duke/DukeException.java @@ -0,0 +1,16 @@ +package duke; + +/** + * The class that extends Exception class. + */ +public class DukeException extends Exception { + + /** + * DukeException constructor. + * + * @param error Takes in the error message. + */ + public DukeException(String error) { + super(error); + } +} diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java new file mode 100644 index 0000000000..e4ef6b4628 --- /dev/null +++ b/src/main/java/duke/Launcher.java @@ -0,0 +1,12 @@ +package duke; + +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); + } +} diff --git a/src/main/java/duke/Main.java b/src/main/java/duke/Main.java new file mode 100644 index 0000000000..1e081d07f1 --- /dev/null +++ b/src/main/java/duke/Main.java @@ -0,0 +1,43 @@ +package duke; + +import java.io.IOException; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + +/** + * A GUI for Duke using FXML. + */ +public class Main extends Application { + + private static Stage stage; + private Duke duke = new Duke(); + + @Override + public void start(Stage stage) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml")); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + Main.stage = stage; + scene.getStylesheets().add(Main.class.getResource("/style/stylesheet.css").toExternalForm()); + Main.stage.setScene(scene); + Main.stage.setResizable(true); + Main.stage.setTitle("Chopper Helpdesk"); + fxmlLoader.getController().setDuke(this.duke); + Main.stage.show(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Close application. + */ + public static void closeStage() { + Main.stage.close(); + } +} diff --git a/src/main/java/duke/MainWindow.java b/src/main/java/duke/MainWindow.java new file mode 100644 index 0000000000..5f03f57a4f --- /dev/null +++ b/src/main/java/duke/MainWindow.java @@ -0,0 +1,67 @@ +package duke; + +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; +import javafx.util.Pair; + +/** + * Controller for MainWindow. Provides the layout for the other controls. + */ +public class MainWindow extends AnchorPane { + @FXML + private Pane background; + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogContainer; + @FXML + private TextField userInput; + @FXML + private Button sendButton; + + private Duke duke; + + private Image userImage = new Image(this.getClass().getResourceAsStream("/images/zoro.png")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/chopper.png")); + + /** + * Initializes the application. + */ + @FXML + public void initialize() { + this.duke = new Duke(); + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + + dialogContainer.getChildren().addAll( + DialogBox.getDukeDialog(duke.showWelcomeMessage(), dukeImage, false) + ); + } + + public void setDuke(Duke d) { + duke = d; + } + + /** + * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to + * the dialog container. Clears the user input after processing. + */ + @FXML + private void handleUserInput() { + String text = userInput.getText().trim(); + Pair response = duke.getResponse(text); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(text, userImage), + DialogBox.getDukeDialog(response.getKey(), dukeImage, response.getValue()) + ); + userInput.clear(); + if (text.equalsIgnoreCase("bye")) { + Main.closeStage(); + } + } +} diff --git a/src/main/java/duke/Parser.java b/src/main/java/duke/Parser.java new file mode 100644 index 0000000000..2be04fe133 --- /dev/null +++ b/src/main/java/duke/Parser.java @@ -0,0 +1,72 @@ +package duke; +import duke.commands.ByeCommand; +import duke.commands.Command; +import duke.commands.DeadlineCommand; +import duke.commands.DeleteCommand; +import duke.commands.EventCommand; +import duke.commands.FindCommand; +import duke.commands.FindDateCommand; +import duke.commands.ListCommand; +import duke.commands.MarkCommand; +import duke.commands.TodoCommand; +import duke.commands.UnmarkCommand; +import duke.commands.UpdateCommand; + + +/** + * The class to generate Command objects. + */ +public class Parser { + + private enum Type { + BYE, LIST, MARK, UNMARK, TODO, DEADLINE, EVENT, DELETE, FIND, FINDDATE, UPDATE + } + + /** + * Creates appropriate Commands based on the input string. + * + * @param input The user's input. + * @throws IllegalArgumentException when user's input does not correspond to any of the cases. + */ + public static Command parse(String input) throws IllegalArgumentException { + String[] words = input.split(" "); + Type t = Type.valueOf(words[0].toUpperCase()); + switch(t) { + case LIST: + return new ListCommand(input); + + case DEADLINE: + return new DeadlineCommand(input); + + case UNMARK: + return new UnmarkCommand(input); + + case TODO: + return new TodoCommand(input); + + case EVENT: + return new EventCommand(input); + + case DELETE: + return new DeleteCommand(input); + + case MARK: + return new MarkCommand(input); + + case FIND: + return new FindCommand(input); + + case FINDDATE: + return new FindDateCommand(input); + + case UPDATE: + return new UpdateCommand(input); + + case BYE: + return new ByeCommand(input); + + default: + throw new IllegalArgumentException(); + } + } +} diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/Storage.java new file mode 100644 index 0000000000..b4562fdfcc --- /dev/null +++ b/src/main/java/duke/Storage.java @@ -0,0 +1,73 @@ +package duke; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * The class to store tasks into a text file. + */ +public class Storage { + private String filePath; + + /** + * Storage constructor. + * + * @param filePath The relative path of the text file that tasks are saved into. + */ + public Storage(String filePath) { + this.filePath = filePath; + } + + /** + * Saves the TaskList into the text file. + * Prints the error message if there is one. + * + * @param tl TaskList that contains objects of type Task. + */ + public void saveTaskList(TaskList tl) { + try { + FileOutputStream fos = new FileOutputStream(this.filePath); + ObjectOutputStream oos = new ObjectOutputStream(fos); + oos.writeObject(tl); + oos.close(); + fos.close(); + System.out.println("Saved"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Loads the TaskList from the text file into the program. + * If file exists, return the TaskList stored in the file. + * If not, return a new TaskList and creates the file. + * + * @return TaskList + */ + public TaskList loadTaskList() { + File file = new File(this.filePath); + if (file.exists()) { + TaskList tl = new TaskList(); + try { + FileInputStream fis = new FileInputStream(this.filePath); + ObjectInputStream ois = new ObjectInputStream(fis); + tl = (TaskList) ois.readObject(); + ois.close(); + fis.close(); + } catch (Exception e) { + System.out.println("File is empty"); + } + return tl; + } else { + try { + file.createNewFile(); + } catch (IOException e) { + System.out.println("Cant create file"); + } + return new TaskList(); + } + } +} diff --git a/src/main/java/duke/TaskList.java b/src/main/java/duke/TaskList.java new file mode 100644 index 0000000000..6176293887 --- /dev/null +++ b/src/main/java/duke/TaskList.java @@ -0,0 +1,114 @@ +package duke; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.stream.Collectors; + +import duke.tasks.Deadline; +import duke.tasks.Event; +import duke.tasks.Task; + + +/** + * The TaskList that stores Task objects. + */ +public class TaskList implements Serializable { + private ArrayList tasks; + + /** + * TaskList constructor. + */ + public TaskList() { + this.tasks = new ArrayList<>(); + } + + /** + * Returns the Task object at the specified index of the ArrayList. + * + * @param i An integer representing the index + * @return Task + */ + public Task get(int i) { + return this.tasks.get(i); + } + + /** + * Adds a Task object into the ArrayList. + * + * @param t The Task object to be added into the ArrayList. + */ + public void add(Task t) { + this.tasks.add(t); + } + + /** + * Returns the size of the ArrayList. + * + * @return int representing the size. + */ + public int size() { + return this.tasks.size(); + } + + /** + * Removes the Task object at the specified index of the ArrayList. + * + * @param i An integer representing the index + */ + public void remove(int i) { + this.tasks.remove(i); + } + + /** + * Filters the tasks in the list by their description. + * @param input The user input. + * @return ArrayList of the filtered tasks. + */ + public ArrayList filter(String input) { + ArrayList filtered = this.tasks.stream() + .filter(task -> task.contains(input)) + .collect(Collectors.toCollection(ArrayList::new)); + return filtered; + } + + /** + * Filters the tasks in the list by their dates. + * @param input The user input. + * @return ArrayList of the filtered tasks. + */ + public ArrayList filterDate(String input) { + ArrayList filtered = this.tasks.stream() + .filter(task -> isDeadlineHasWord(task, input) || isEventHasWord(task, input)) + .collect(Collectors.toCollection(ArrayList::new)); + return filtered; + } + + /** + * Checks if the task is an Event and contains the word in their date. + * @param t The Task. + * @param keyword The input string. + * @return Boolean + */ + private boolean isEventHasWord(Task t, String keyword) { + return (t instanceof Event) && ((Event) t).dateContains(keyword); + } + + /** + * Checks if the task is an Deadline and contains the word in their date. + * @param t The Task. + * @param keyword The input string. + * @return Boolean + */ + private boolean isDeadlineHasWord(Task t, String keyword) { + return (t instanceof Deadline) && ((Deadline) t).dateContains(keyword); + } + + /** + * @inheritDoc + */ + @Override + public String toString() { + return "" + this.tasks; + } + +} diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/Ui.java new file mode 100644 index 0000000000..43337722b0 --- /dev/null +++ b/src/main/java/duke/Ui.java @@ -0,0 +1,184 @@ +package duke; +import java.util.ArrayList; + +import duke.tasks.Task; + +/** + * The class for the UI of the program. + */ +public class Ui { + public static String showLoadingError() { + return "Unable to load tasks from storage"; + } + + /** + * Returns the welcome message on start. + * + * @return The String message. + */ + public static String showWelcomeMessage() { + return "Hello I'm chopper\n" + + "My commands are the following:\n" + + " 1. todo \n" + + " 2. deadline /by \n" + + " 3. event /from " + + "/to \n" + + " 4. delete \n" + + " 5. mark \n" + + " 6. unmark \n" + + " 7. list\n" + + " 8. find \n" + + " 9. finddate \n" + + " 10. update / \n" + + " 11. bye\n" + + "What can I do for you?"; + } + + public static String showByeMessage() { + return "Bye. Hope to see you again soon!"; + } + + /** + * Return tasks in the list. + * + * @param tasks The tasks. + * @return String The message. + */ + public static String printTasks(ArrayList tasks) { + String result = "Here are the tasks in your list:\n"; + for (int i = 0; i < tasks.size(); i++) { + int num = i + 1; + result += " " + + num + + ". " + + tasks.get(i) + + "\n"; + } + return result; + } + + /** + * Mark task confirmation message. + * @param task The task to be marked. + * @return String The message. + */ + public static String markTaskConfirmation(Task task) { + return "Nice! I've marked this task as done:\n " + task; + } + + /** + * Unmark task confirmation message. + * @param task The task to be unmarked. + * @return String The message. + */ + public static String unmarkTaskConfirmation(Task task) { + return "OK, I've marked this task as not done yet:\n " + task; + } + + /** + * Returns the confirmation message after performing an action. + * + * @param action String of the action. + * @param tasks TaskList of all tasks. + * @param task The task in question. + * @return String The message itself. + */ + public static String confirmationMessage(String action, TaskList tasks, Task task) { + String result = "Got it. I've " + action + " this task:\n" + + " " + + task; + if (!action.equals("updated")) { + result += "\nNow you have " + + tasks.size() + + " tasks in the list."; + } + return result; + } + public static String emptyDescriptionError() { + return "The description of a task cannot be empty."; + } + + /** + * Return wrong deadline command format message. + * + * @return String The message. + */ + public static String wrongDeadlineCommandFormat() { + return "Deadline must be in the format\n" + + "deadline /by "; + } + + /** + * Return wrong deadline date format message. + * + * @return String The message. + */ + public static String wrongDeadlineDateFormat() { + return "Deadline must have a date of the following format:\n" + + "1. yyyy-MM-dd\n" + + "2. yyyy-MM-dd HHmm"; + } + /** + * Return wrong event command format message. + * + * @return String The message. + */ + public static String wrongEventCommandFormat() { + return "Event must be in the format\n" + + "event /from /to "; + } + + /** + * Return wrong event date format message. + * + * @return String The message. + */ + public static String wrongEventDateFormat() { + return "Event must have start and end dates of the following format:\n" + + "1. yyyy-MM-dd\n" + + "2. yyyy-MM-dd HHmm"; + } + /** + * Return wrong update command format message. + * + * @return String The message. + */ + public static String wrongUpdateFormat() { + return "Update command must be in the format\nupdate / "; + } + + public static String wrongFindCommandFormat() { + return "The find command is in the wrong format."; + } + + public static String wrongFindDateCommand() { + return "The finddate command is in the wrong format."; + } + + public static String insufficientTasksMessage() { + return "There are insufficient tasks."; + } + public static String noTasksMessage() { + return "There are no tasks."; + } + + + public static String datePassed() { + return "Date has passed."; + } + + public static String startDatelaterThanEnd() { + return "End date is earlier than start date."; + } + public static String unrecognisedCommand() { + return "Unrecognised command. Try again."; + } + + public static String missingIndex() { + return "Command must be followed by an integer."; + } + + public static String notDeadline() { + return "Task does not have a deadline"; + } +} diff --git a/src/main/java/duke/commands/ByeCommand.java b/src/main/java/duke/commands/ByeCommand.java new file mode 100644 index 0000000000..459596fc34 --- /dev/null +++ b/src/main/java/duke/commands/ByeCommand.java @@ -0,0 +1,42 @@ +package duke.commands; +import duke.DukeException; +import duke.Storage; +import duke.TaskList; +import duke.Ui; + +/** + * The class for the Bye command which extends Command class. + */ +public class ByeCommand extends Command { + private String input; + + /** + * ByeCommand constructor. + * + * @param input The user's input. + */ + public ByeCommand(String input) { + this.input = input; + } + + /** + * @inheritDoc + */ + public String execute(TaskList tasks, Storage storage) throws DukeException { + if (this.input.trim().equalsIgnoreCase("bye")) { + storage.saveTaskList(tasks); + return Ui.showByeMessage(); + } else { + throw new DukeException(Ui.unrecognisedCommand()); + } + + } + + /** + * @inheritDoc + */ + @Override + public boolean isExit() { + return true; + } +} diff --git a/src/main/java/duke/commands/Command.java b/src/main/java/duke/commands/Command.java new file mode 100644 index 0000000000..e4777c7287 --- /dev/null +++ b/src/main/java/duke/commands/Command.java @@ -0,0 +1,30 @@ +package duke.commands; +import duke.DukeException; +import duke.Storage; +import duke.TaskList; + +/** + * The abstract class for command. + */ +public abstract class Command { + + /** + * Executes the command. + * Prints appropriate error messages. + * + * @param tasks TaskList + * @param storage The Storage object used to save the edited TaskList. + * @return True if the execution is successful, false if it's not. + */ + public abstract String execute(TaskList tasks, Storage storage) throws DukeException; + + /** + * Overridden by ByeCommand only. + * + * @return True if the program needs to terminate, false if it does not. + */ + public boolean isExit() { + return false; + } +} + diff --git a/src/main/java/duke/commands/DeadlineCommand.java b/src/main/java/duke/commands/DeadlineCommand.java new file mode 100644 index 0000000000..70f8d79c16 --- /dev/null +++ b/src/main/java/duke/commands/DeadlineCommand.java @@ -0,0 +1,61 @@ +package duke.commands; + +import java.time.format.DateTimeParseException; + +import duke.DukeException; +import duke.Storage; +import duke.TaskList; +import duke.Ui; +import duke.tasks.Deadline; + +/** + * The class for the Deadline command which extends Command class. + */ +public class DeadlineCommand extends Command { + private String input; + + /** + * DeadlineCommand constructor. + * + * @param input The user's input. + */ + public DeadlineCommand(String input) { + this.input = input; + } + + /** + * Checks if the format of the input is valid. + * + * @param indexBy index of the /by in the input. + * @return a boolean + */ + private boolean checkFormat(int indexBy) { + return indexBy < 0 + || indexBy - 1 < 9 + || indexBy + 4 > input.length() + || !input.substring(indexBy, indexBy + 4).equals("/by "); + } + /** + * @inheritDoc + */ + public String execute(TaskList tasks, Storage storage) throws DukeException { + try { + int indexBy = input.indexOf("/"); + String[] words = this.input.split(" "); + if (words.length <= 1) { + throw new DukeException(Ui.emptyDescriptionError()); + } + if (checkFormat(indexBy)) { + throw new DukeException(Ui.wrongDeadlineCommandFormat()); + } + Deadline d = new Deadline(input.substring(9, indexBy - 1), + input.substring(indexBy + 4, input.length())); + + tasks.add(d); + storage.saveTaskList(tasks); + return Ui.confirmationMessage("added", tasks, d); + } catch (DateTimeParseException e) { + throw new DukeException(Ui.wrongDeadlineDateFormat()); + } + } +} diff --git a/src/main/java/duke/commands/DeleteCommand.java b/src/main/java/duke/commands/DeleteCommand.java new file mode 100644 index 0000000000..a1d4cf8a8c --- /dev/null +++ b/src/main/java/duke/commands/DeleteCommand.java @@ -0,0 +1,43 @@ +package duke.commands; +import duke.DukeException; +import duke.Storage; +import duke.TaskList; +import duke.Ui; +import duke.tasks.Task; + +/** + * The class for the Delete command which extends Command class. + */ +public class DeleteCommand extends Command { + private String input; + + /** + * DeleteCommand constructor. + * + * @param input The user's input. + */ + public DeleteCommand(String input) { + this.input = input; + } + + /** + * @inheritDoc + */ + public String execute(TaskList tasks, Storage storage) throws DukeException { + try { + if (input.length() <= 7) { + throw new DukeException(Ui.missingIndex()); + } + int index = Integer.parseInt(input.substring(7)); + if (index > tasks.size() || index <= 0) { + throw new DukeException(Ui.insufficientTasksMessage()); + } + Task task = tasks.get(index - 1); + tasks.remove(index - 1); + storage.saveTaskList(tasks); + return Ui.confirmationMessage("deleted", tasks, task); + } catch (NumberFormatException e) { + throw new DukeException(Ui.missingIndex()); + } + } +} diff --git a/src/main/java/duke/commands/EventCommand.java b/src/main/java/duke/commands/EventCommand.java new file mode 100644 index 0000000000..9b01b8f0c7 --- /dev/null +++ b/src/main/java/duke/commands/EventCommand.java @@ -0,0 +1,67 @@ +package duke.commands; + +import java.time.format.DateTimeParseException; + +import duke.DukeException; +import duke.Storage; +import duke.TaskList; +import duke.Ui; +import duke.tasks.Event; + +/** + * The class for the Event command which extends Command class. + */ +public class EventCommand extends Command { + private String input; + + /** + * EventCommand constructor. + * + * @param input The user's input. + */ + public EventCommand(String input) { + this.input = input; + } + + /** + * Checks if the format of the input is valid. + * + * @param indexFrom the index of the /from. + * @param indexTo the index of the /to. + * @return a boolean + */ + private boolean checkFormat(int indexFrom, int indexTo) { + return indexFrom < -1 + || indexFrom - 1 < 6 + || (indexFrom + 6 > indexTo - 1) + || (indexTo + 4 > this.input.length()) + || (!(this.input.substring(indexFrom, indexFrom + 6).equals("/from ") + && this.input.substring(indexTo, indexTo + 4).equals("/to "))); + } + + /** + * @inheritDoc + */ + public String execute(TaskList tasks, Storage storage) throws DukeException { + try { + int indexFrom = input.indexOf("/"); + int indexTo = input.lastIndexOf("/"); + String[] words = this.input.split(" "); + if (words.length <= 1) { + throw new DukeException(Ui.emptyDescriptionError()); + } + if (checkFormat(indexFrom, indexTo)) { + throw new DukeException(Ui.wrongEventCommandFormat()); + } + + Event e = new Event(input.substring(6, indexFrom - 1), + input.substring(indexFrom + 6, indexTo - 1), + input.substring(indexTo + 4, input.length())); + tasks.add(e); + storage.saveTaskList(tasks); + return Ui.confirmationMessage("added", tasks, e); + } catch (DateTimeParseException e) { + throw new DukeException(Ui.wrongEventDateFormat()); + } + } +} diff --git a/src/main/java/duke/commands/FindCommand.java b/src/main/java/duke/commands/FindCommand.java new file mode 100644 index 0000000000..f4b672cd3a --- /dev/null +++ b/src/main/java/duke/commands/FindCommand.java @@ -0,0 +1,42 @@ +package duke.commands; +import java.util.ArrayList; + +import duke.DukeException; +import duke.Storage; +import duke.TaskList; +import duke.Ui; +import duke.tasks.Task; + + +/** + * The class for the Find command which extends Command class. + */ +public class FindCommand extends Command { + private String input; + + /** + * FindCommand constructor. + * + * @param input The user's input. + */ + public FindCommand(String input) { + this.input = input; + } + + /** + * @inheritDoc + */ + public String execute(TaskList tasks, Storage storage) throws DukeException { + String[] words = this.input.trim().split(" "); + if (words.length <= 1) { + throw new DukeException(Ui.wrongFindCommandFormat()); + } + String keyword = this.input.trim().substring(5, input.length()); + ArrayList filtered = tasks.filter(keyword.toLowerCase()); + if (filtered.size() == 0) { + return Ui.noTasksMessage(); + } + + return Ui.printTasks(filtered); + } +} diff --git a/src/main/java/duke/commands/FindDateCommand.java b/src/main/java/duke/commands/FindDateCommand.java new file mode 100644 index 0000000000..ebfdfa64bd --- /dev/null +++ b/src/main/java/duke/commands/FindDateCommand.java @@ -0,0 +1,42 @@ +package duke.commands; +import java.util.ArrayList; + +import duke.DukeException; +import duke.Storage; +import duke.TaskList; +import duke.Ui; +import duke.tasks.Task; + +/** + * The class for the FindDate command which extends Command class. + */ +public class FindDateCommand extends Command { + private String input; + + /** + * FindDateCommand constructor. + * + * @param input The user's input. + */ + public FindDateCommand(String input) { + this.input = input; + } + + /** + * @inheritDoc + */ + public String execute(TaskList tasks, Storage storage) throws DukeException { + String[] words = this.input.trim().split(" "); + if (words.length <= 1) { + throw new DukeException(Ui.wrongFindDateCommand()); + } + String keyword = this.input.trim().substring(9, input.length()); + ArrayList filtered = tasks.filterDate(keyword.toLowerCase()); + System.out.println(filtered.size()); + if (filtered.size() == 0) { + return Ui.noTasksMessage(); + } + + return Ui.printTasks(filtered); + } +} diff --git a/src/main/java/duke/commands/ListCommand.java b/src/main/java/duke/commands/ListCommand.java new file mode 100644 index 0000000000..4dd483f029 --- /dev/null +++ b/src/main/java/duke/commands/ListCommand.java @@ -0,0 +1,39 @@ +package duke.commands; +import duke.Storage; +import duke.TaskList; +import duke.Ui; + +/** + * The class for the List command which extends Command class. + */ +public class ListCommand extends Command { + private String input; + + /** + * ListCommand constructor. + * + * @param input The user's input. + */ + public ListCommand(String input) { + this.input = input; + } + + /** + * @inheritDoc + */ + public String execute(TaskList tasks, Storage storage) { + if (tasks.size() <= 0) { + return Ui.noTasksMessage(); + } + String result = "Here are the tasks in your list:\n"; + for (int i = 0; i < tasks.size(); i++) { + int num = i + 1; + result += " " + + num + + ". " + + tasks.get(i) + + "\n"; + } + return result; + } +} diff --git a/src/main/java/duke/commands/MarkCommand.java b/src/main/java/duke/commands/MarkCommand.java new file mode 100644 index 0000000000..c1b423dcab --- /dev/null +++ b/src/main/java/duke/commands/MarkCommand.java @@ -0,0 +1,44 @@ +package duke.commands; +import duke.DukeException; +import duke.Storage; +import duke.TaskList; +import duke.Ui; +import duke.tasks.Task; + +/** + * The class for the Mark command which extends Command class. + */ +public class MarkCommand extends Command { + private String input; + + /** + * MarkCommand constructor. + * + * @param input The user's input. + */ + public MarkCommand(String input) { + this.input = input; + } + + /** + * @inheritDoc + */ + public String execute(TaskList tasks, Storage storage) throws DukeException { + try { + if (input.length() <= 5) { + throw new DukeException(Ui.missingIndex()); + } + + int index = Integer.parseInt(input.substring(5)); + if (index > tasks.size() || index <= 0) { + throw new DukeException(Ui.insufficientTasksMessage()); + } + Task task = tasks.get(index - 1); + task.mark(); + storage.saveTaskList(tasks); + return Ui.markTaskConfirmation(task); + } catch (NumberFormatException nfe) { + throw new DukeException(Ui.missingIndex()); + } + } +} diff --git a/src/main/java/duke/commands/TodoCommand.java b/src/main/java/duke/commands/TodoCommand.java new file mode 100644 index 0000000000..50e9a64e01 --- /dev/null +++ b/src/main/java/duke/commands/TodoCommand.java @@ -0,0 +1,36 @@ +package duke.commands; +import duke.DukeException; +import duke.Storage; +import duke.TaskList; +import duke.Ui; +import duke.tasks.Todo; + +/** + * The class for the Todo command which extends Command class. + */ +public class TodoCommand extends Command { + private String input; + + /** + * TodoCommand constructor. + * + * @param input The user's input. + */ + public TodoCommand(String input) { + this.input = input; + } + + /** + * @inheritDoc + */ + public String execute(TaskList tasks, Storage storage) throws DukeException { + String[] words = this.input.split(" "); + if (words.length <= 1) { + throw new DukeException(Ui.emptyDescriptionError()); + } + Todo td = new Todo(input.substring(5, input.length())); + tasks.add(td); + storage.saveTaskList(tasks); + return Ui.confirmationMessage("added", tasks, td); + } +} diff --git a/src/main/java/duke/commands/UnmarkCommand.java b/src/main/java/duke/commands/UnmarkCommand.java new file mode 100644 index 0000000000..0060099a9a --- /dev/null +++ b/src/main/java/duke/commands/UnmarkCommand.java @@ -0,0 +1,45 @@ +package duke.commands; +import duke.DukeException; +import duke.Storage; +import duke.TaskList; +import duke.Ui; +import duke.tasks.Task; + + +/** + * The class for the Unmark command which extends Command class. + */ +public class UnmarkCommand extends Command { + private String input; + + /** + * UnmarkCommand constructor. + * + * @param input The user's input. + */ + public UnmarkCommand(String input) { + this.input = input; + } + + /** + * @inheritDoc + */ + public String execute(TaskList tasks, Storage storage) throws DukeException { + try { + if (input.length() <= 7) { + throw new DukeException(Ui.missingIndex()); + } + int index = Integer.parseInt(input.substring(7)); + if (index > tasks.size() || index <= 0) { + throw new DukeException(Ui.insufficientTasksMessage()); + } + Task task = tasks.get(index - 1); + task.unmark(); + storage.saveTaskList(tasks); + return Ui.unmarkTaskConfirmation(task); + + } catch (NumberFormatException e) { + throw new DukeException(Ui.missingIndex()); + } + } +} diff --git a/src/main/java/duke/commands/UpdateCommand.java b/src/main/java/duke/commands/UpdateCommand.java new file mode 100644 index 0000000000..8580a79dcf --- /dev/null +++ b/src/main/java/duke/commands/UpdateCommand.java @@ -0,0 +1,54 @@ +package duke.commands; + +import duke.DukeException; +import duke.Storage; +import duke.TaskList; +import duke.Ui; +import duke.tasks.Deadline; +import duke.tasks.Task; + +/** + * The class for the Update command which extends Command class. + */ +public class UpdateCommand extends Command { + private String input; + + /** + * TodoCommand constructor. + * + * @param input The user's input. + */ + public UpdateCommand(String input) { + this.input = input; + } + + /** + * @inheritDoc + */ + public String execute(TaskList tasks, Storage storage) throws DukeException { + try { + String[] words = this.input.split(" "); + int index = Integer.parseInt(words[1]); + if (index > tasks.size() || index <= 0) { + throw new DukeException(Ui.insufficientTasksMessage()); + } + Task t = tasks.get(index - 1); + + if (words[2].equals("/description")) { + t.update(this.input.split("/description")[1].trim()); + } else if (words[2].equals("/deadline") && (t instanceof Deadline)) { + Deadline d = (Deadline) t; + d.updateDeadline(this.input.split("/deadline")[1].trim()); + } else if (words[2].equals("/deadline") && !(t instanceof Deadline)) { + throw new DukeException(Ui.notDeadline()); + } else { + throw new DukeException(Ui.wrongUpdateFormat()); + } + storage.saveTaskList(tasks); + return Ui.confirmationMessage("updated", tasks, t); + + } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { + throw new DukeException(Ui.wrongUpdateFormat()); + } + } +} diff --git a/src/main/java/duke/tasks/Deadline.java b/src/main/java/duke/tasks/Deadline.java new file mode 100644 index 0000000000..cedf750df5 --- /dev/null +++ b/src/main/java/duke/tasks/Deadline.java @@ -0,0 +1,83 @@ +package duke.tasks; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +import duke.DukeException; +import duke.Ui; + + + +/** + * The Deadline class that extends Task. + */ +public class Deadline extends Task { + private String by; + /** + * Constructor for a Deadline object. + * + * @param description The Event description. + * @param date The date of the Deadline. + */ + public Deadline(String description, String date) throws DukeException { + super(description); + LocalDateTime byDateTime; + + try { + byDateTime = LocalDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm")); + this.by = byDateTime.format(DateTimeFormatter.ofPattern("MMM dd yyyy hh:mm a")); + } catch (DateTimeParseException e) { + LocalDate byDate = LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + this.by = byDate.format(DateTimeFormatter.ofPattern("MMM dd yyyy")); + byDateTime = byDate.atTime(23, 59); + } + if (!byDateTime.isAfter(LocalDateTime.now())) { + throw new DukeException(Ui.datePassed()); + } + } + + /** + * Check if the deadline's date contains the given keyword. + * + * @param keyword The keyword argument. + * @return A boolean value. + */ + public boolean dateContains(String keyword) { + if (this.by.length() >= keyword.length()) { + return this.by.toLowerCase().contains(keyword.toLowerCase()); + } else { + return false; + } + } + + /** + * Updates the deadline attribute of the object. + * + * @param date String input of the new date. + * @throws DukeException + */ + public void updateDeadline(String date) throws DukeException { + LocalDateTime newByDateTime; + + try { + newByDateTime = LocalDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm")); + this.by = newByDateTime.format(DateTimeFormatter.ofPattern("MMM dd yyyy hh:mm a")); + } catch (DateTimeParseException e) { + LocalDate byDate = LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + this.by = byDate.format(DateTimeFormatter.ofPattern("MMM dd yyyy")); + newByDateTime = byDate.atTime(23, 59); + } + if (!newByDateTime.isAfter(LocalDateTime.now())) { + throw new DukeException(Ui.datePassed()); + } + } + + /** + * @inheritDoc + */ + @Override + public String toString() { + return "[D]" + super.toString() + "\n (by: " + this.by + ")\n"; + } +} diff --git a/src/main/java/duke/tasks/Event.java b/src/main/java/duke/tasks/Event.java new file mode 100644 index 0000000000..fe16783a98 --- /dev/null +++ b/src/main/java/duke/tasks/Event.java @@ -0,0 +1,75 @@ +package duke.tasks; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +import duke.DukeException; +import duke.Ui; + +/** + * The Event object that extends Task. + */ +public class Event extends Task { + private String from; + private String to; + + /** + * Constructor for an Event object. + * + * @param description The Event description. + * @param from The start date of the Event. + * @param to The end date of the Event. + */ + public Event(String description, String from, String to) throws DukeException { + super(description); + LocalDateTime fromDateTime; + LocalDateTime toDateTime; + try { + fromDateTime = LocalDateTime.parse(from, DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm")); + + this.from = fromDateTime.format(DateTimeFormatter.ofPattern("MMM dd yyyy hh:mm a")); + } catch (DateTimeParseException e) { + LocalDate fromDate = LocalDate.parse(from, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + this.from = fromDate.format(DateTimeFormatter.ofPattern("MMM dd yyyy")); + fromDateTime = fromDate.atStartOfDay(); + } + if (!fromDateTime.isAfter(LocalDateTime.now())) { + throw new DukeException(Ui.datePassed()); + } + try { + toDateTime = LocalDateTime.parse(to, DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm")); + this.to = toDateTime.format(DateTimeFormatter.ofPattern("MMM dd yyyy hh:mm a")); + } catch (DateTimeParseException e) { + LocalDate toDate = LocalDate.parse(to, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + this.to = toDate.format(DateTimeFormatter.ofPattern("MMM dd yyyy")); + toDateTime = toDate.atTime(23, 59); + } + if (!toDateTime.isAfter(fromDateTime)) { + throw new DukeException(Ui.startDatelaterThanEnd()); + } + } + + /** + * Check if the event's dates contains the given keyword. + * + * @param keyword The keyword argument. + * @return A boolean value. + */ + public boolean dateContains(String keyword) { + keyword = keyword.toLowerCase(); + if ((this.from.length() >= keyword.length()) || (this.to.length() >= keyword.length())) { + return this.from.toLowerCase().contains(keyword) || this.to.toLowerCase().contains(keyword); + } else { + return false; + } + } + + /** + * @inheritDoc + */ + @Override + public String toString() { + return "[E]" + super.toString() + "\n (from: " + from + " to: " + to + ")\n"; + } +} diff --git a/src/main/java/duke/tasks/Task.java b/src/main/java/duke/tasks/Task.java new file mode 100644 index 0000000000..c82b6df958 --- /dev/null +++ b/src/main/java/duke/tasks/Task.java @@ -0,0 +1,68 @@ +package duke.tasks; +import java.io.Serializable; + +/** + * The Task object that implements Serializable. + */ +public class Task implements Serializable { + private String description; + private boolean isDone; + + /** + * Constructor for a Task object. + * + * @param description The Event description. + */ + public Task(String description) { + this.description = description.trim(); + this.isDone = false; + } + + /** + * Check if the task's description contains the given keyword. + * + * @param keyword The keyword argument. + * @return A boolean value. + */ + public boolean contains(String keyword) { + if (description.length() >= keyword.length()) { + return description.toLowerCase().contains(keyword); + } else { + return false; + } + } + + /** + * Method to mark the task as done. + */ + public void mark() { + if (!this.isDone) { + this.isDone = true; + } + } + + /** + * Method to unmark the task as not done. + */ + public void unmark() { + if (this.isDone) { + this.isDone = false; + } + } + + public void update(String newDescription) { + this.description = newDescription; + } + + /** + * @inheritDoc + */ + @Override + public String toString() { + if (this.isDone) { + return String.format(" [ X ] " + this.description); + } else { + return String.format(" [ ] " + this.description); + } + } +} diff --git a/src/main/java/duke/tasks/Todo.java b/src/main/java/duke/tasks/Todo.java new file mode 100644 index 0000000000..8870ba7746 --- /dev/null +++ b/src/main/java/duke/tasks/Todo.java @@ -0,0 +1,24 @@ +package duke.tasks; + +/** + * The Todo object that extends Task. + */ +public class Todo extends Task { + + /** + * Constructor for a Todo object. + * + * @param description The Todo description. + */ + public Todo(String description) { + super(description); + } + + /** + * @inheritDoc + */ + @Override + public String toString() { + return "[T]" + super.toString() + "\n"; + } +} 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/chopper.png b/src/main/resources/images/chopper.png new file mode 100644 index 0000000000..02d369bcd7 Binary files /dev/null and b/src/main/resources/images/chopper.png differ diff --git a/src/main/resources/images/water.png b/src/main/resources/images/water.png new file mode 100644 index 0000000000..0e3f435e2d Binary files /dev/null and b/src/main/resources/images/water.png differ diff --git a/src/main/resources/images/zoro.png b/src/main/resources/images/zoro.png new file mode 100644 index 0000000000..9d7d1e5985 Binary files /dev/null and b/src/main/resources/images/zoro.png differ diff --git a/src/main/resources/style/stylesheet.css b/src/main/resources/style/stylesheet.css new file mode 100644 index 0000000000..fe702dda45 --- /dev/null +++ b/src/main/resources/style/stylesheet.css @@ -0,0 +1,19 @@ +.button{ + -fx-text-fill: rgb(49, 89, 23); + -fx-border-color: rgb(49, 89, 23); + -fx-border-radius: 5; + -fx-padding: 3 6 6 6; +} +.root { + -fx-background-image: url("../images/water.png"); + -fx-background-size: cover; + -fx-background-position: center center; +} +#scrollPane { + -fx-background: transparent; + -fx-background-color: transparent; +} +#dialog { + -fx-background-radius: 10px; + -fx-text-fill: white; +} diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..3d72550382 --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..58598d45f0 --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + +