diff --git a/README.md b/README.md index 8715d4d915..c07eec22e2 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,32 @@ -# Duke project template - -This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. - -## Setting up in Intellij - -Prerequisites: JDK 11, update Intellij to the most recent version. - -1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project first) -1. Open the project into Intellij as follows: - 1. Click `Open`. - 1. Select the project directory, and click `OK`. - 1. If there are any further prompts, accept the defaults. -1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
- In the same dialog, set the **Project language level** field to the `SDK default` option. -3. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: - ``` - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - ``` +# GARY + +> "Get busy living or get busy dying." - Stephen King [source](https://atlasmedstaff.com/adventures-in-nursing/get-busy-living-or-get-busy-dying/) + +Gary frees your mind of having to remember things you need to do. It's, + +* text-based +* easy to learn +* ~~FAST~~ SUPER FAST to use + +All you need to do is, + +1. download it from [here.](https://github.com/vanessaxuuan/ip/releases/download/A-Release/ip-all.jar) +2. double-click it. +3. add your tasks. +4. let it manage your tasks for you 😉 + +**And it is FREE!** + +Features: +- [x] Managing tasks +- [x] Managing deadlines (coming soon) +- [ ] Reminders (coming soon) + +If you Java programmer, you can use it to practice Java too. Here's the **main** method: +``` +public class Main { + public static void main(String[] args) { + new Gary("./filepath).run(); + } +} +``` diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..93cfdd78fd --- /dev/null +++ b/build.gradle @@ -0,0 +1,70 @@ +plugins { + id 'java' + id 'application' + id 'checkstyle' + id 'com.github.johnrengelman.shadow' version '5.1.0' +} + +repositories { + mavenCentral() +} + +dependencies { + String javaFxVersion = '11' + + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' + + 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' +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + +application { + mainClassName = "gary.Launcher" +} + +//shadowJar { +// archiveBaseName = "Launcher" +// archiveClassifier = null +//} + +jar { + manifest { + attributes( + 'Main-Class': 'gary.Launcher' + ) + } +} + +//checkstyle { +// toolVersion = '8.29' +//} + +run{ + enableAssertions = true + standardInput = System.in +} \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 8077118ebe..92f49866a4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,104 @@ # User Guide +Gary is a desktop app for managing your daily to-do lists, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). ## Features +1. [Adding tasks](https://github.com/vanessaxuuan/ip/blob/master/docs/README.md#adding-tasks): task_type +2. [Deleting tasks](https://github.com/vanessaxuuan/ip/blob/master/docs/README.md#deleting-tasks): delete +3. [Finding related tasks](https://github.com/vanessaxuuan/ip/blob/master/docs/README.md#finding-related-tasks): find +4. [Listing out tasks](https://github.com/vanessaxuuan/ip/blob/master/docs/README.md#listing-out-tasks): list +5. [Viewing help](https://github.com/vanessaxuuan/ip/blob/master/docs/README.md#viewing-help): help +6. [Marking tasks](https://github.com/vanessaxuuan/ip/blob/master/docs/README.md#marking-and-un-marking-tasks): mark +7. [Interactions](https://github.com/vanessaxuuan/ip/blob/master/docs/README.md#interactions) -### Feature-ABC -Description of the feature. +### 1. Adding Tasks -### Feature-XYZ +##### Adding different types of Task to your to-do list: -Description of the feature. +1. ToDo: -## Usage +``` +[todo task_name] e.g. todo errands +``` + +2. Event + +``` +[event name / dd-mm-yyyy,hhmm] e.g. event party / 25-03-2022,2359 +``` + +3. Deadline + +``` +[deadline name / dd-mm-yyyy,hhmm] e.g. deadline lab4 / 05-12-2022,1400 +``` + +### 2. Deleting Tasks + +##### Delete Task(s) + +``` +[delete index ...] e.g. delete 5 3 1 or delete 5 +``` +note: to delete multiple tasks at the same time, enter index in descending order + +##### Delete all Tasks + +``` +[refresh] +``` + +### 3. Finding related Tasks +``` +[find keyword] e.g. find assignment +``` + +Output: A lists of tasks containing the keyword + + +### 4. Listing out Tasks +``` +[list] +``` + Output: Current to-do list + + +### 5. Viewing Help +``` +[help] +``` +Output: command summary -### `Keyword` - Describe action -Describe the action and its outcome. +### 6. Marking and un-marking Tasks -Example of usage: +##### Mark task(s) as done + +``` +[mark index ...] e.g. mark 1 4 2 +``` +note: can mark multiple tasks at the same time + +outcome: e.g. T[X] errands + + +##### Mark task(s) as not done + +``` +[unmark index ...] e.g. unmark 1 5 2 +``` +note: can un-mark multiple tasks at the same time -`keyword (optional arguments)` +outcome: e.g. T[ ] errands -Expected outcome: -Description of the outcome. +### 7. Interactions +Input: ``` -expected output +1. hi +2. hello +3. hey +4. bye ``` +#### Try out these inputs to interact with Gary! 😉 diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..2790e668f1 Binary files /dev/null and b/docs/Ui.png differ diff --git a/gary.txt b/gary.txt new file mode 100644 index 0000000000..ee53e2ee0e --- /dev/null +++ b/gary.txt @@ -0,0 +1,4 @@ +1. T[ ] groceries +2. E[ ] birthday on: 19 Dec 2022 23:59 pm +3. D[ ] assignment by: 01 Mar 2022 14:00 pm +4. T[ ] run 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/gary/Gary.java b/src/main/java/gary/Gary.java new file mode 100644 index 0000000000..e25f9e3ab1 --- /dev/null +++ b/src/main/java/gary/Gary.java @@ -0,0 +1,44 @@ +package gary; +import gary.exception.GaryException; +import gary.ui.Parser; +import gary.ui.Storage; +import gary.ui.TaskList; + +/** + * Represents a chat-bot that manages your daily to-do list + */ +public class Gary { + + private static Storage storage; + protected static TaskList tasks; + protected static Parser parser; + + /** + * Initializes Storage, TaskList, Ui and Parser required + * + * @param filePath file path to file containing history of chat-bot + */ + public Gary(String filePath) { + storage = new Storage(filePath); + parser = new Parser(); + tasks = new TaskList(storage.loadFile()); + } + + /** + * Function to generate Gary's response to user input + * + * @param input user input + * @return Gary's response + */ + protected String getResponse(String input) { + String resp; + try { + resp = parser.parse(input, tasks); + } catch (GaryException e) { + return e.garyError(); + } catch (AssertionError e) { + return "Sorry... What do you need to do again?"; + } + return resp; + } +} \ No newline at end of file diff --git a/src/main/java/gary/Launcher.java b/src/main/java/gary/Launcher.java new file mode 100644 index 0000000000..5fea0ea27f --- /dev/null +++ b/src/main/java/gary/Launcher.java @@ -0,0 +1,12 @@ +package gary; +import javafx.application.Application; + +/** + * Launches Gary + */ +public class Launcher { + + public static void main(String[] args) { + Application.launch(Main.class, args); + } +} diff --git a/src/main/java/gary/Main.java b/src/main/java/gary/Main.java new file mode 100644 index 0000000000..e0beebcefa --- /dev/null +++ b/src/main/java/gary/Main.java @@ -0,0 +1,39 @@ +package gary; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * A GUI for Gary using FXML + */ +public class Main extends Application { + + private static Path file = Paths.get(".","gary.txt"); + private static Gary gary = new gary.Gary("./gary.txt"); + + /** + * Initialize relevant variables and start the GUI + * + * @param stage main stage + */ + @Override + public void start(Stage stage) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml")); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + stage.setScene(scene); + fxmlLoader.getController().setGary(gary); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/gary/MainWindow.java b/src/main/java/gary/MainWindow.java new file mode 100644 index 0000000000..993f3cfe28 --- /dev/null +++ b/src/main/java/gary/MainWindow.java @@ -0,0 +1,62 @@ +package gary; + +import gary.gui.DialogBox; + +import javafx.fxml.FXML; + +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; + +/** + * Controller for MainWindow. Provides the layout for the other controls. + */ +public class MainWindow extends AnchorPane { + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogContainer; + @FXML + private TextField userInput; + @FXML + private Button sendButton; + + private Gary gary; + + private Image userImage = new Image(this.getClass().getResourceAsStream("/user.png")); + private Image garyImage = new Image(this.getClass().getResourceAsStream("/gary2.png")); + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + } + + /** + * Set Gary from Main class + * + * @param d Gary to be used + */ + public void setGary(Gary d) { + gary = d; + dialogContainer.getChildren().add( + DialogBox.getWelcomeMessage(garyImage)); + } + + /** + * 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 userText = userInput.getText(); + String garyText = gary.getResponse(userInput.getText()); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(userText, userImage), + DialogBox.getGaryDialog(garyText, garyImage) + ); + userInput.clear(); + } +} diff --git a/src/main/java/gary/exception/GaryException.class b/src/main/java/gary/exception/GaryException.class new file mode 100644 index 0000000000..494e075fb4 Binary files /dev/null and b/src/main/java/gary/exception/GaryException.class differ diff --git a/src/main/java/gary/exception/GaryException.java b/src/main/java/gary/exception/GaryException.java new file mode 100644 index 0000000000..815f5aa805 --- /dev/null +++ b/src/main/java/gary/exception/GaryException.java @@ -0,0 +1,28 @@ +package gary.exception; + +import java.lang.Exception; + +/** + * Represents Exceptions that will be encountered by Gary + * Invoked by invalid user input + */ +public class GaryException extends Exception { + private String msg; + + /** + * Constructs a new Gary Exception with given message + * + * @param msg invalid user input + */ + public GaryException(String msg) { + this.msg = msg; + } + + /** + * Prints out error message + */ + public String garyError() { + assert !msg.isBlank(); + return "Sorry, what is " + this.msg; + } +} diff --git a/src/main/java/gary/gui/DialogBox.class b/src/main/java/gary/gui/DialogBox.class new file mode 100644 index 0000000000..0bcb5e007c Binary files /dev/null and b/src/main/java/gary/gui/DialogBox.class differ diff --git a/src/main/java/gary/gui/DialogBox.java b/src/main/java/gary/gui/DialogBox.java new file mode 100644 index 0000000000..e3974f1c05 --- /dev/null +++ b/src/main/java/gary/gui/DialogBox.java @@ -0,0 +1,100 @@ +package gary.gui; + +import gary.MainWindow; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Insets; +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.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.HBox; +import javafx.scene.paint.Paint; + +import java.io.IOException; +import java.util.Collections; + +/** + * An example of a custom control using FXML. + * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label + * containing text from the speaker. + */ +public class DialogBox extends HBox { + @FXML + private Label dialog; + @FXML + private ImageView displayPicture; + + private DialogBox(String text, Image img) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + // DialogBox background colour + CornerRadii space = new CornerRadii(5); + Insets offset = new Insets(5); + Paint shade = Paint.valueOf("rgba(50, 110, 110, 0.3)"); + BackgroundFill fill = new BackgroundFill(shade, space, offset); + this.setBackground(new Background(fill)); + + dialog.setText(text); + displayPicture.setImage(img); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + Collections.reverse(tmp); + getChildren().setAll(tmp); + setAlignment(Pos.TOP_LEFT); + } + + /** + * + * @param img + * @return + */ + public static DialogBox getWelcomeMessage(Image img) { + var db = new DialogBox(" Gary here~", img); + db.flip(); + return db; + } + + /** + * Creates DialogBox for User + * + * @param text user input + * @param img user display picture + * @return DialogBox for user + */ + public static DialogBox getUserDialog(String text, Image img) { + return new DialogBox(text, img); + } + + /** + * Creates DialogBox for Gary + * + * @param text gary's response + * @param img Gary display picture + * @return DialogBox for Gary + */ + public static DialogBox getGaryDialog(String text, Image img) { + var db = new DialogBox(text, img); + db.flip(); + return db; + } +} + diff --git a/src/main/java/gary/task/Deadline.class b/src/main/java/gary/task/Deadline.class new file mode 100644 index 0000000000..e3b315c4b3 Binary files /dev/null and b/src/main/java/gary/task/Deadline.class differ diff --git a/src/main/java/gary/task/Deadline.java b/src/main/java/gary/task/Deadline.java new file mode 100644 index 0000000000..ce5bcda7a3 --- /dev/null +++ b/src/main/java/gary/task/Deadline.java @@ -0,0 +1,42 @@ +package gary.task; +/** + * Represents a Task that entails a deadline + * A completed DeadLine e.g. D[X] deadline_name by: 19-01-2022 10:30pm + */ +public class Deadline extends Task { + private String date; + + /** + * Constructs a new DeadLine object + * + * @param str deadline name + * @param date due date + */ + public Deadline(String str, String date) { + super(str); + this.date = date; + } + + /** + * Checks if Deadline name or date matches a specific keyword + * Invoked by gary.TaskList::find + * + * @param str keyword + * @return Whether the Deadline contains a specific keyword + */ + @Override + public boolean contain(String str) { + return date.contains(str) || super.contain(str); + } + + /** + * Represents Deadline as a String object + * e.g. D[X] deadline_name by: 09-12-2022 10:30pm + * + * @return Deadline representation in String + */ + @Override + public String toString() { + return "D" + super.toString() + " by: " + date; + } +} diff --git a/src/main/java/gary/task/Event.class b/src/main/java/gary/task/Event.class new file mode 100644 index 0000000000..8993defab7 Binary files /dev/null and b/src/main/java/gary/task/Event.class differ diff --git a/src/main/java/gary/task/Event.java b/src/main/java/gary/task/Event.java new file mode 100644 index 0000000000..f0ad56d5af --- /dev/null +++ b/src/main/java/gary/task/Event.java @@ -0,0 +1,43 @@ +package gary.task; + +/** + * Represents a Task that happens on a specific day/time + * A completed Event e.g. E[X] event_name on: 19-01-2022 10:30pm + */ +public class Event extends Task { + private String date; + + /** + * Constructs a new Event object + * + * @param str Event name + * @param date day of Event + */ + public Event(String str, String date) { + super(str); + this.date = date; + } + + /** + * Checks if Event name or date matches a specific keyword + * Invoked by gary.TaskList::find + * + * @param str keyword + * @return Whether the Event contains a specific keyword + */ + @Override + public boolean contain(String str) { + return date.contains(str) || super.contain(str); + } + + /** + * Represents Event as a String object + * e.g. E[X] event_name on: 05-12-2022 23:59pm + * + * @return Deadline representation in String + */ + @Override + public String toString() { + return "E" + super.toString() + " on: " + date; + } +} diff --git a/src/main/java/gary/task/Task.class b/src/main/java/gary/task/Task.class new file mode 100644 index 0000000000..c5b21049bd Binary files /dev/null and b/src/main/java/gary/task/Task.class differ diff --git a/src/main/java/gary/task/Task.java b/src/main/java/gary/task/Task.java new file mode 100644 index 0000000000..3fd96cbb02 --- /dev/null +++ b/src/main/java/gary/task/Task.java @@ -0,0 +1,57 @@ +package gary.task; + +/** + * Represents a task + * A completed task e.g. [X] task_name + */ +public class Task { + protected String task; + protected boolean isDone; + + /** + * Constructs a new Task object + * + * @param task task name + */ + public Task(String task) { + this.task = task; + this.isDone = false; + } + + /** + * Marks a Task as completed e.g. [X] + */ + public void toMark() { + this.isDone = true; + } + + /** + * Marks a Task as incomplete e.g. [ ] + */ + public void toUnmark() { + this.isDone = false; + } + + /** + * Checks if the Task matches a specific keyword + * Invoked by gary.TaskList::find + * + * @param str keyword + * @return Whether the Task contains a specific keyword + */ + public boolean contain(String str) { + return task.contains(str); + } + + /** + * Represents Task as a String object + * Completed Task e.g. [X] task_name + * + * @return Task representation in String + */ + @Override + public String toString() { + String done = this.isDone ? "X" : " "; + return "[" + done + "] " + this.task; + } +} \ No newline at end of file diff --git a/src/main/java/gary/task/ToDo.class b/src/main/java/gary/task/ToDo.class new file mode 100644 index 0000000000..1c5cab7c00 Binary files /dev/null and b/src/main/java/gary/task/ToDo.class differ diff --git a/src/main/java/gary/task/ToDo.java b/src/main/java/gary/task/ToDo.java new file mode 100644 index 0000000000..eb9b6103bc --- /dev/null +++ b/src/main/java/gary/task/ToDo.java @@ -0,0 +1,28 @@ +package gary.task; + +/** + * Represents a Task to be done + * A completed ToDo Task e.g. T[X] workout + */ +public class ToDo extends Task { + + /** + * Constructs a new ToDo Task + * + * @param str name of ToDo task + */ + public ToDo(String str) { + super(str); + } + + /** + * Represents ToDo as a String object + * e.g. T[ ] toDo_task + * + * @return ToDo representation in String + */ + @Override + public String toString() { + return "T" + super.toString(); + } +} diff --git a/src/main/java/gary/ui/Parser.class b/src/main/java/gary/ui/Parser.class new file mode 100644 index 0000000000..12b25b1335 Binary files /dev/null and b/src/main/java/gary/ui/Parser.class differ diff --git a/src/main/java/gary/ui/Parser.java b/src/main/java/gary/ui/Parser.java new file mode 100644 index 0000000000..d85140dbd9 --- /dev/null +++ b/src/main/java/gary/ui/Parser.java @@ -0,0 +1,66 @@ +package gary.ui; + +import gary.exception.GaryException; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +/** + * Helps to decode the user input and + * invoke the corresponding operation to be done + */ +public class Parser { + + /** + * Parse user input + * + * @param input User input + * @param tsk TaskList to be used + * @return indication to end of user input + */ + public String parse(String input, TaskList tsk) throws GaryException { + assert !input.isBlank(); // catches empty input + try { + String[] type = input.split(" "); + String theTask = type[0]; + switch (theTask) { + case "bye": + return "Bye, have a productive day!"; + case "hi": + case "hello": + case "hey": + return theTask + "! Enter if you're unfamiliar with me!"; + case "todo": + return tsk.addTodo(input.substring(5)); + case "event": + String[] e = input.split("/", 5); + return tsk.addEvent(e[0].substring(6), parseDate(e)); + case "deadline": + String[] d = input.split("/", 5); + return tsk.addDeadline(d[0].substring(9), parseDate(d)); + case "find": + return tsk.find(input.substring(5)); + } + } catch (StringIndexOutOfBoundsException e) { + return "Ah please enter a valid description e.g. task_type name / date"; + } catch (DateTimeParseException e) { + return "Ah please enter a valid date e.g. 19-01-2022,2359"; + } + return tsk.invoke(input); + } + + /** + * Convert representation of date and time + * e.g. from 19-01-2001,2359 to 19 Jan 2001 23:59pm + * + * @param d User input + * @return date and time + */ + private static String parseDate(String[] d) { + DateTimeFormatter inFormat = DateTimeFormatter.ofPattern("dd-MM-yyyy,HHmm"); + LocalDateTime d1 = LocalDateTime.parse(d[1].strip(), inFormat); + DateTimeFormatter outFormat = DateTimeFormatter.ofPattern("dd LLL yyyy HH:mm a"); + return d1.format(outFormat); + } +} diff --git a/src/main/java/gary/ui/Storage.class b/src/main/java/gary/ui/Storage.class new file mode 100644 index 0000000000..f1b51f95ba Binary files /dev/null and b/src/main/java/gary/ui/Storage.class differ diff --git a/src/main/java/gary/ui/Storage.java b/src/main/java/gary/ui/Storage.java new file mode 100644 index 0000000000..bece822409 --- /dev/null +++ b/src/main/java/gary/ui/Storage.java @@ -0,0 +1,76 @@ +package gary.ui; + +import gary.task.Task; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Scanner; +import java.io.FileOutputStream; + +/** + * Represents a storage for files used by Gary + * Access history of chat-bot to provide + * user with pre-recorded/incomplete to-do list + */ +public class Storage { + private static String file; + + /** + * Constructs a new Storage object + * + * @param path path to file + */ + public Storage(String path) { + file = path; + } + + /** + * Access and loads the content of the saved file + * + * @return content of file + */ + public ArrayList loadFile() { + ArrayList content = new ArrayList<>(); + try { + File f = new File(file); + Scanner sc = new Scanner(f); + while (sc.hasNext()) { + content.add(sc.nextLine()); + } + } catch (FileNotFoundException e) { + System.out.println("New file created:"); + File newFile = new File("./gary.txt"); + } + return content; + } + + /** + * Saves updated TaskList in the File + * + * @param list TaskList to be saved + */ + public static void saveTask(ArrayList list) { + try { + if (list.isEmpty()) { + FileWriter fw = new FileWriter(file); + fw.write(" "); + fw.close(); + } else { + FileWriter fw = new FileWriter(file); + fw.write("1. " + list.get(0).toString() + System.lineSeparator()); // overwrite first line + fw.close(); + FileWriter fw2 = new FileWriter(file, true); + int len = list.size(); + for (int i = 1; i < len; i++) { + fw2.write(i + 1 + ". " + list.get(i).toString() + System.lineSeparator()); // append + } + fw2.close(); + } + } catch (IOException e) { + System.out.println("No file to save into???"); + } + } +} \ No newline at end of file diff --git a/src/main/java/gary/ui/TaskList.class b/src/main/java/gary/ui/TaskList.class new file mode 100644 index 0000000000..a38a7f1839 Binary files /dev/null and b/src/main/java/gary/ui/TaskList.class differ diff --git a/src/main/java/gary/ui/TaskList.java b/src/main/java/gary/ui/TaskList.java new file mode 100644 index 0000000000..c7717d57f1 --- /dev/null +++ b/src/main/java/gary/ui/TaskList.java @@ -0,0 +1,247 @@ +package gary.ui; + +import gary.exception.GaryException; +import gary.task.Deadline; +import gary.task.Event; +import gary.task.Task; +import gary.task.ToDo; + +import java.util.ArrayList; + +/** + * Represents a list of tasks to be done by user + * Contains a set of operations to be performed on the list + */ +public class TaskList { + public static ArrayList tasks; + + /** + * Constructs a new TaskList from a List of String + * + * @param t List of String to be decoded + */ + public TaskList(ArrayList t) { + tasks = new ArrayList(); + this.decode(t); + } + + /** + * Convert List of Strings into List of Tasks + * + * @param lst List of Strings to be decoded into commands + */ + public void decode(ArrayList lst) { + for (String str : lst) { + char type = str.charAt(3); + boolean isDone = str.charAt(5) == 'X'; + switch (type) { + case 'T': + this.tasks.add(new ToDo(str.substring(8))); + break; + case 'E': + String[] e = str.split("on:", 5); + this.tasks.add(new Event(e[0].substring(8), e[1])); + break; + case 'D': + String[] d = str.split("by:", 5); + this.tasks.add(new Deadline(d[0].substring(8), d[1])); + break; + } + if (isDone) { + int len = this.tasks.size(); + this.tasks.get(len - 1).toMark(); + } + } + } + + /** + * Calls the method corresponding to user input + * + * @param input operation to be performed + * @throws GaryException if method is invalid + */ + public String invoke(String input) throws GaryException { + try { + String[] type = input.split(" "); + switch (type[0]) { + case "list": + return this.showList(2); + case "mark": + return this.mark(input); + case "unmark": + return this.unmark(input); + case "delete": + return this.delete(input); + case "help": + return this.showHelp(); + case "refresh": + return this.deleteAll(); + default: + throw new GaryException(input); + } + } catch (AssertionError e) { + return "Your To-Do List is empty! :0"; + } + } + + /** + * Prints out Task List + * + * @param x type of message to be printed + */ + public String showList(int x) { + String msg = x == 1 ? "This is your current to-do list:" : "to do list:"; + if (this.tasks.isEmpty()) { + return "No history recorded, what would you like to do today?"; + } else { + int i = 1; + for (Task curr : this.tasks) { + msg += System.lineSeparator(); + msg += i + ". " + curr.toString(); + i++; + } + } + return msg; + } + + /** + * Adds a ToDo Task + * + * @param t task to do + */ + public String addTodo(String t) { + ToDo next = new ToDo(t); + tasks.add(next); + Storage.saveTask(tasks); + return "task added!"; + } + + /** + * Adds an Event Task + * + * @param e Event to add + * @param date day of event + */ + public String addEvent(String e, String date) { + Event next = new Event(e,date); + tasks.add(next); + Storage.saveTask(tasks); + return "task added!"; + } + + /** + * Adds a Deadline Task + * + * @param d DeadLines to add + * @param date due date + */ + public String addDeadline(String d, String date) { + Deadline next = new Deadline(d,date); + tasks.add(next); + Storage.saveTask(tasks); + return "task added!"; + } + + /** + * Deletes unnecessary tasks + */ + public String delete(String input) { + assert !tasks.isEmpty(); + String[] seq = input.split(" "); + String out = ""; + try { + int len = seq.length; + int prev = len; // max + for(int i = 1; i < len; i++) { + int curr = Integer.parseInt(seq[i]) - 1; + int check = prev - curr; + // maintain descending order + assert check > 0; + tasks.remove(curr); + prev = curr; + } + Storage.saveTask(tasks); + } catch (IndexOutOfBoundsException e) { + return "Please enter a valid number or sequence e.g. 5 3 1" + System.lineSeparator() + + this.showList(1); + } catch (AssertionError e) { + return "Something went wrong... please enter tasks in descending order! e.g. 5 3 1" + + System.lineSeparator() + this.showList(1); + } + return "Done!"; + } + + /** + * Marks completed task as done + */ + public String mark(String input) { + assert !tasks.isEmpty(); + String[] seq = input.split(" "); + int len = seq.length; + for(int i = 1; i < len; i++) { + tasks.get(Integer.parseInt(seq[i]) - 1).toMark(); + } + Storage.saveTask(tasks); + return "Alright, updated!"; + } + + /** + * Undo marking + */ + public String unmark(String input) { + assert !tasks.isEmpty(); + String[] seq = input.split(" "); + int len = seq.length; + for(int i = 1; i < len; i++) { + tasks.get(Integer.parseInt(seq[i]) - 1).toUnmark(); + } + Storage.saveTask(tasks); + return "Alright, updated!"; + } + + /** + * Print out Tasks that contains keyword even partially + * + * @param keyword used to find related tasks + */ + public String find(String keyword) { + String end = "Here are the matching tasks in your list:" + System.lineSeparator(); + int i = 0; + for(Task t : tasks) { + if (t.contain(keyword)) { + i++; + end += i + ". " + t.toString() + System.lineSeparator(); + } + } + if (i == 0) { + end = "no match found :("; + } + return end; + } + + public String showHelp() { + String help = "Chat with me!!!" + System.lineSeparator(); + help += System.lineSeparator(); + help += "add Tasks:" + System.lineSeparator(); + help += "[todo task_name]" + System.lineSeparator(); + help += "[event name / 19-01-2022,2359]" + System.lineSeparator(); + help += "[deadline name / 19-01-2022,2359]" + System.lineSeparator(); + help += System.lineSeparator(); + + help += "more functions:" + System.lineSeparator(); + help += "[list]: list out current todo-list" + System.lineSeparator(); + help += "[delete 2 1]: delete tasks 2 and 1 (descending order) " + System.lineSeparator(); + help += "[refresh]: delete all tasks" + System.lineSeparator(); + help += "[mark 2 3]: indicating tasks 2 & 3 as done" + System.lineSeparator(); + help += "[unmark 2 3]: indicating tasks 2 & 3 as incomplete (default)" + System.lineSeparator(); + help += "[find key word]: find tasks related to keyword(s)" + System.lineSeparator(); + return help; + } + + public String deleteAll() { + assert !tasks.isEmpty(); + tasks.clear(); + Storage.saveTask(tasks); + return "To-Do List cleared! What would you like to do today? ^^"; + } +} diff --git a/src/main/resources/DaGary.png b/src/main/resources/DaGary.png new file mode 100644 index 0000000000..232ed9dc82 Binary files /dev/null and b/src/main/resources/DaGary.png differ diff --git a/src/main/resources/DaUser.png b/src/main/resources/DaUser.png new file mode 100644 index 0000000000..82c1adaaf9 Binary files /dev/null and b/src/main/resources/DaUser.png differ diff --git a/src/main/resources/SceneFill.png b/src/main/resources/SceneFill.png new file mode 100644 index 0000000000..82322a017c Binary files /dev/null and b/src/main/resources/SceneFill.png differ diff --git a/src/main/resources/gary.png b/src/main/resources/gary.png new file mode 100644 index 0000000000..653811a7c2 Binary files /dev/null and b/src/main/resources/gary.png differ diff --git a/src/main/resources/gary2.png b/src/main/resources/gary2.png new file mode 100644 index 0000000000..937b836cf8 Binary files /dev/null and b/src/main/resources/gary2.png differ diff --git a/src/main/resources/user.png b/src/main/resources/user.png new file mode 100644 index 0000000000..ab70a83c4d Binary files /dev/null and b/src/main/resources/user.png differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..f17b7e81ad --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..390d786f3d --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + +