diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000000..133b0e7f2b --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,34 @@ +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 \ No newline at end of file diff --git a/.gitignore b/.gitignore index f69985ef1f..862d164975 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ bin/ /text-ui-test/ACTUAL.txt text-ui-test/EXPECTED-UNIX.TXT +data/duke.txt diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..9612a498fa --- /dev/null +++ b/build.gradle @@ -0,0 +1,61 @@ +plugins { + id 'java' + id 'application' + id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'checkstyle' +} + +checkstyle { + toolVersion = '10.2' +} + +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 = "duke.Duke" +} + +shadowJar { + archiveBaseName = "duke" + archiveClassifier = null +} + +run{ + standardInput = System.in +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..62281d3553 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..dcaa1af3c3 --- /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..2867ced4ab 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,331 @@ # User Guide -## Features +Duke is an app for managing tasks. +It uses a Command Line Interface (CLI) with a basic Graphical User Interface (GUI). +It is optimized for users who can type fast and have some experience with CLIs. -### Feature-ABC -Description of the feature. +## Features -### Feature-XYZ +### Task List + +- 3 types of tasks: ToDos, Deadlines, and Events +- Creating, displaying, and deleting tasks +- Marking and unmarking of tasks as done +- Searching of tasks by name +- Sorting of tasks by name -Description of the feature. ## Usage -### `Keyword` - Describe action +### `help` - View help + +Displays a list of available commands. + +#### Example of usage: + +`help` + +#### Expected output: + +``` +Available commands: + +hello +bye +list +mark +unmark +todo +deadline +event +delete +find +sort +help +``` + +
+ + +### `bye` - Exit application + +Saves the current task list and quits the application. + +#### Example of usage: + +`bye` + +#### Expected outcome: + +Duke displays the exit message, saves and quits. The window is only closed the +next time the user sends an input (i.e. presses enter or the send button). + +#### Expected output: + +``` +Goodbye +``` + +
+ + +### `list` - View all tasks + +Displays all tasks in a list format. + +#### Example of usage: + +`list` + +#### Expected outcome: + +Duke displays all tasks in a numbered list, along with information about the +whether the task is done, and the date of the task (if any). + +#### Expected output: + +``` +1. [E][X] midterms 1 (at: 2022-02-02) +2. [E][ ] midterms 2 (at: 2022-01-01) +3. [D][X] assignment 7 (by: 2022-01-01) +4. [T][X] project D + +You have 4 task(s) in your list +``` + +
+ + +### `mark` - Mark a task as done + +Sets the completion status of the task at the specified index as done. +If the task is already marked as done, nothing will be changed. + +#### Command format: + +`mark [index of the task]` + +#### Example of usage: + +`mark 2` + +#### Expected outcome: + +Duke marks the task at index 2 as done. + +#### Expected output: + +``` +Task marked as complete: +[E][X] midterms 2 (at: 2022-01-01) +``` + +
+ + +### `unmark` - Mark a task as not done + +Sets the completion status of the task at the specified index as not done. +If the task is already marked as not done, nothing will be changed. + +#### Command format: + +`unmark [index of the task]` + +#### Example of usage: + +`unmark 1` + +#### Expected outcome: + +Duke marks the task at index 1 as not done. + +#### Expected output: + +``` +Task marked as incomplete: +[E][ ] midterms 1 (at: 2022-02-02) +``` + +
+ + +### `todo` - Create a ToDo + +Adds a ToDo task to the list. ToDos are tasks that do not have +a date associated with them. + +#### Command format: + +`todo [name of the task]` + +#### Example of usage: + +`todo set up repository` + +#### Expected outcome: -Describe the action and its outcome. +Duke creates a ToDo task called 'set up repository' and adds it to the list of tasks. -Example of usage: +#### Expected output: + +``` +Task added: +[T][ ] set up repository +You now have 5 task(s) in your list +``` + +
+ + +### `deadline` - Create a Deadline + +Adds a Deadline task to the list. Deadlines are tasks that need to be +completed by a certain date. + +#### Command format: + +`deadline [name of the task] /by [date in the format YYYY-MM-DD]` + +#### Example of usage: + +`deadline implement feature X /by 2022-10-31` + +#### Expected outcome: + +Duke creates a Deadline task called "**implement feature X**" that should +be completed by "**2022-10-31 (31st October 2022)**" and +adds it to the list of tasks. + +#### Expected output: + +``` +Task added: +[D][ ] implement feature x (by: 2022-10-31) +You now have 6 task(s) in your list +``` + +
-`keyword (optional arguments)` -Expected outcome: +### `event` - Create an Event -Description of the outcome. +Adds a Event task to the list. Events are tasks that start at a certain date. + +#### Command format: + +`event [name of the task] /at [date in the format YYYY-MM-DD]` + +#### Example of usage: + +`event product demo /at 2022-11-01` + +#### Expected outcome: + +Duke creates an Event task called "**product demo**" that +starts on "**2022-11-301 (1st November 2022)**" and +adds it to the list of tasks. + +#### Expected output: ``` -expected output +Task added: +[E][ ] product demo (at: 2022-11-01) +You now have 7 task(s) in your list ``` + +
+ + +### `delete` - Delete a task + +Deletes the task at the specified index from the list. + +#### Command format: + +`delete [index of the task]` + +#### Example of usage: + +`delete 1` + +#### Expected outcome: + +Duke removes the task at index 1 (the first task) from the list. + +#### Expected output: + +``` +Task deleted: +[E][ ] midterm 1 (at: 2022-02-02) +You now have 6 task(s) in your list +``` + +
+ + +### `find` - Search for tasks + +Looks up and displays a list of tasks whose name contains the specified +string of text. + +#### Command format: + +`find [string of text to check for]` + +#### Example of usage: + +`find midt` + +#### Expected outcome: + +Duke displays a list of tasks whose name contains "**midt**". + +#### Expected output: + +``` +Displaying search results for "midt": +1. [E][ ] midterms 1 (at: 2022-02-02) +2. [E][ ] midterms 2 (at: 2022-01-01) + +2 task(s) found +``` + +
+ + +### `sort` - Sort tasks by name + +Sorts all tasks in the list by name alphabetically in ascending or +descending order, specified by /a and /d respectively, then displays the list. + +#### Command format: + +`sort /a` - sort in ascending order + +`sort /d` - sort in descending order + +#### Example of usage: + +`sort /a` + +#### Expected outcome: + +Duke sorts all tasks by name in alphabetical ascending order, then displays +the list. + +#### Expected output: + +``` +Tasks sorted by name in ascending order +1. [D][ ] implement feature x (by: 2022-10-31) +2. [E][ ] midterms 1 (at: 2022-02-02) +3. [E][ ] midterms 2 (at: 2022-01-01) +4. [E][ ] product demo (at: 2022-11-01) +5. [T][ ] set up repository + +You have 5 task(s) in your list +``` + +
diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..b215607b4a 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/Deadline.java b/src/main/java/duke/Deadline.java new file mode 100644 index 0000000000..8d1459ae6d --- /dev/null +++ b/src/main/java/duke/Deadline.java @@ -0,0 +1,34 @@ +package duke; + +import java.time.LocalDate; + +/** + * Class used to represent a task that has a due date. + */ +public class Deadline extends Task { + public static final String TASK_TYPE_CHARACTER = "D"; + + protected LocalDate dueDate; + + /** + * The constructor for a Deadline task. + * + * @param taskName A string that is the name of the task. + * @param isDone A boolean that represents whether this task is complete. + * @param dueDate A LocalDate that contains information about when this task is due. + */ + public Deadline(String taskName, boolean isDone, LocalDate dueDate) { + super(taskName, isDone); + this.dueDate = dueDate; + } + + @Override + public String toSaveFormatString() { + return String.format("%s|%d|%s|%s", TASK_TYPE_CHARACTER, isDone ? 1 : 0, taskName, dueDate); + } + + @Override + public String toString() { + return String.format("[%s]%s (by: %s)", TASK_TYPE_CHARACTER, super.toString(), dueDate); + } +} diff --git a/src/main/java/duke/DialogueBox.java b/src/main/java/duke/DialogueBox.java new file mode 100644 index 0000000000..96787df366 --- /dev/null +++ b/src/main/java/duke/DialogueBox.java @@ -0,0 +1,71 @@ +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.paint.Paint; +import javafx.scene.text.Font; + +/** + * This control represents a dialogue box consisting of an ImageView to represent the speaker's face and a label + * containing text from the speaker. + */ +public class DialogueBox extends HBox { + private static final String USER_COLOR = "#00FFFF"; + private static final String DUKE_COLOR = "#FFFF00"; + + @FXML + private Label dialogue; + @FXML + private ImageView displayPicture; + + private DialogueBox(String text, Image img, String bgColor) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogueBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + dialogue.setText(text); + dialogue.setTextFill(Paint.valueOf(bgColor.toString())); + displayPicture.setImage(img); + } + + @FXML + public void initialize() { + dialogue.setFont(Font.font("Cascadia Mono", 12)); + } + + /** + * 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 DialogueBox getUserDialogue(String text, Image img) { + return new DialogueBox(text, img, USER_COLOR); + } + + public static DialogueBox getDukeDialogue(String text, Image img) { + var db = new DialogueBox(text, img, DUKE_COLOR); + db.flip(); + return db; + } +} diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java new file mode 100644 index 0000000000..255b55be4c --- /dev/null +++ b/src/main/java/duke/Duke.java @@ -0,0 +1,108 @@ +package duke; + +import java.io.IOException; + +import javafx.application.Application; + +/** + * The main class for the Duke program. + */ +public class Duke { + private static boolean isRunning; + private static StringBuilder response = new StringBuilder(); + + /** + * Starts the Duke program and performs the necessary start up operations. + * This includes displaying the greeting response and loading the task list from file. + */ + public static void initialize() { + try { + Storage.loadData(); + } catch (IOException e) { + addToResponse("Error: Failed to access data"); + } + + Ui.displayStartUpText(); + + isRunning = true; + } + + /** + * Stops the Duke program and performs the necessary exit operations. + * This includes displaying the exit response and saving the task list to file. + */ + public static void exit() { + try { + Storage.saveData(); + } catch (IOException e) { + addToResponse("Error: Failed to access data"); + } + + Ui.displayExitText(); + + isRunning = false; + } + + /** + * Gives Duke an input string to process using Parser. + * Parser will generate a command based on this input, and perform operations. + * Ui then creates a response to the user and passes it to Duke via addToResponse. + * + * @param input The input string to be processed by Parser. + */ + public static void giveInput(String input) { + Ui.generateLine(); + try { + Parser.parseInput(input); + } catch (DukeException e) { + addToResponse(e.getMessage() + "\n"); + } + Ui.generateLine(); + } + + /** + * Gives Duke a string store as a response to the user. + * When getResponse is called, the string is retrieved for displaying. + * + * @param output The response string to be stored. + */ + public static void addToResponse(String output) { + response.append(output); + + System.out.print(output); + } + + /** + * Gets a string that is a response to the user's last command. + * + * @return A string that is a response to the user's last command. + */ + public static String getResponse() { + return response.toString(); + } + + /** + * Clears the response to be displayed to the user when getResponse is called. + */ + public static void clearResponse() { + response = new StringBuilder(); + } + + /** + * Gets whether Duke is still running. + * + * @return True if Duke is running, otherwise false. + */ + public static boolean getIsRunning() { + return isRunning; + } + + /** + * Runs the Duke program. + * + * @param args The command line arguments. + */ + public static void main(String[] args) { + Application.launch(Main.class, args); + } +} diff --git a/src/main/java/duke/DukeException.java b/src/main/java/duke/DukeException.java new file mode 100644 index 0000000000..1f394b45e5 --- /dev/null +++ b/src/main/java/duke/DukeException.java @@ -0,0 +1,10 @@ +package duke; + +/** + * A class used to represent Exceptions specific to Duke + */ +public class DukeException extends Exception { + public DukeException(String message) { + super(message); + } +} diff --git a/src/main/java/duke/Event.java b/src/main/java/duke/Event.java new file mode 100644 index 0000000000..ee87caacab --- /dev/null +++ b/src/main/java/duke/Event.java @@ -0,0 +1,34 @@ +package duke; + +import java.time.LocalDate; + +/** + * Class used to represent a task that has a start date. + */ +public class Event extends Task { + public static final String TASK_TYPE_CHARACTER = "E"; + + protected LocalDate eventDate; + + /** + * The constructor for an Event. + * + * @param taskName A string that is the name of the task. + * @param isDone A boolean that represents whether this task is complete. + * @param eventDate A LocalDate that contains information about the start date of this task. + */ + public Event(String taskName, boolean isDone, LocalDate eventDate) { + super(taskName, isDone); + this.eventDate = eventDate; + } + + @Override + public String toSaveFormatString() { + return String.format("%s|%d|%s|%s", TASK_TYPE_CHARACTER, isDone ? 1 : 0, taskName, eventDate); + } + + @Override + public String toString() { + return String.format("[%s]%s (at: %s)", TASK_TYPE_CHARACTER, super.toString(), eventDate); + } +} diff --git a/src/main/java/duke/Main.java b/src/main/java/duke/Main.java new file mode 100644 index 0000000000..5307db8714 --- /dev/null +++ b/src/main/java/duke/Main.java @@ -0,0 +1,36 @@ +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 { + @Override + public void start(Stage stage) { + try { + Duke.initialize(); + + assert Duke.getIsRunning() + : "Duke.getIsRunning() should be true if Duke.initialize() runs correctly"; + + FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml")); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + stage.setScene(scene); + MainWindow mainWindow = fxmlLoader.getController(); + mainWindow.setStage(stage); + mainWindow.getResponse(); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + + } + } +} diff --git a/src/main/java/duke/MainWindow.java b/src/main/java/duke/MainWindow.java new file mode 100644 index 0000000000..449fcf8704 --- /dev/null +++ b/src/main/java/duke/MainWindow.java @@ -0,0 +1,73 @@ +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.VBox; +import javafx.stage.Stage; + +/** + * Controller for MainWindow. Provides the layout for the other controls. + */ +public class MainWindow extends AnchorPane { + private static final String BACKGROUND_COLOR = "#111111"; + + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogueContainer; + @FXML + private TextField userInput; + @FXML + private Button sendButton; + + private Stage stage; + + private Image userImage = new Image(this.getClass().getResourceAsStream("/images/UserIcon.png")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/DukeIcon.png")); + + /** + * Sets up GUI components after creation. + */ + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogueContainer.heightProperty()); + dialogueContainer.setStyle(String.format("-fx-background-color: %s;", BACKGROUND_COLOR)); + } + + public void setStage(Stage stage) { + this.stage = stage; + } + + /** + * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to + * the dialog container. Clears the user input after processing. + */ + @FXML + private void handleUserInput() { + String input = userInput.getText(); + dialogueContainer.getChildren().addAll( + DialogueBox.getUserDialogue(input, userImage) + ); + + if (!Duke.getIsRunning()) { + stage.close(); + return; + } + + Duke.giveInput(input); + getResponse(); + + userInput.clear(); + } + + public void getResponse() { + dialogueContainer.getChildren().add( + DialogueBox.getDukeDialogue(Duke.getResponse(), dukeImage) + ); + Duke.clearResponse(); + } +} diff --git a/src/main/java/duke/Parser.java b/src/main/java/duke/Parser.java new file mode 100644 index 0000000000..5442756f4b --- /dev/null +++ b/src/main/java/duke/Parser.java @@ -0,0 +1,98 @@ +package duke; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A class that handles user input. + */ +public class Parser { + private static final String GREET_COMMAND = "hello"; + private static final String EXIT_COMMAND = "bye"; + private static final String DISPLAY_COMMAND = "list"; + private static final String MARK_COMMAND = "mark"; + private static final String UNMARK_COMMAND = "unmark"; + private static final String CREATE_TODO_COMMAND = "todo"; + private static final String CREATE_DEADLINE_COMMAND = "deadline"; + private static final String CREATE_EVENT_COMMAND = "event"; + private static final String DELETE_COMMAND = "delete"; + private static final String FIND_COMMAND = "find"; + private static final String SORT_COMMAND = "sort"; + private static final String HELP_COMMAND = "help"; + + /** + * Parses an input string and calls the relevant method (if any). + * + * @param input The input string to be parsed. + */ + public static void parseInput(String input) throws DukeException { + String[] words = input.toLowerCase().split(" ", 2); + String command = words[0]; + String args = ""; + if (words.length > 1) { + args = words[1]; + } + + switch (command) { + case GREET_COMMAND: + Ui.displayGreeting(); + break; + case EXIT_COMMAND: + Duke.exit(); + break; + case DISPLAY_COMMAND: + Ui.displayTasks(); + break; + case MARK_COMMAND: + case UNMARK_COMMAND: + boolean isDone = command.equals("mark"); + Ui.displayMarkTaskMessage(TaskList.markTask(isDone, args), isDone); + break; + case CREATE_TODO_COMMAND: + Ui.displayAddTaskMessage(TaskList.addToDo(args)); + break; + case CREATE_DEADLINE_COMMAND: + Ui.displayAddTaskMessage(TaskList.addDeadline(args)); + break; + case CREATE_EVENT_COMMAND: + Ui.displayAddTaskMessage(TaskList.addEvent(args)); + break; + case DELETE_COMMAND: + Ui.displayDeleteTaskMessage(TaskList.deleteTask(args)); + break; + case FIND_COMMAND: + Ui.displaySearchTasksMessage(TaskList.searchTasks(args), args); + break; + case SORT_COMMAND: + Ui.displaySortTasksByNameMessage(TaskList.sortTaskListByName(args)); + break; + case HELP_COMMAND: + Ui.displayCommands(); + break; + default: + throw new DukeException("Command not recognised"); + } + } + + /** + * Gets a list of commands recognized by the Parser. + * + * @return An arraylist of strings that are recognized commands. + */ + public static List getCommandList() { + return new ArrayList<>(Arrays.asList( + GREET_COMMAND, + EXIT_COMMAND, + DISPLAY_COMMAND, + MARK_COMMAND, + UNMARK_COMMAND, + CREATE_TODO_COMMAND, + CREATE_DEADLINE_COMMAND, + CREATE_EVENT_COMMAND, + DELETE_COMMAND, + FIND_COMMAND, + SORT_COMMAND, + HELP_COMMAND)); + } +} diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/Storage.java new file mode 100644 index 0000000000..406b00f7a5 --- /dev/null +++ b/src/main/java/duke/Storage.java @@ -0,0 +1,114 @@ +package duke; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.time.format.DateTimeParseException; +import java.util.Scanner; + +/** + * A class that handles loading and saving tasks in the file. + */ +public class Storage { + private static final String DATA_FILE_PATH = "data"; + private static final String DATA_FILE_NAME = "duke.txt"; + + /** + * Attempts to load saved tasks from the hard disk. + * Creates the save file and directory if missing. + * + * @throws IOException If the save file or file path could not be accessed. + */ + public static void loadData() throws IOException { + Path parentDir = Paths.get(DATA_FILE_PATH); + if (!Files.exists(parentDir)) { + Files.createDirectories(parentDir); + } + + File dataFile = new File(Paths.get(parentDir.toString(), DATA_FILE_NAME).toString()); + if (!dataFile.exists()) { + dataFile.createNewFile(); + } + + TaskList.clearTaskList(); + + Scanner scanner = new Scanner(dataFile); + while (scanner.hasNextLine()) { + String saveFormatString = scanner.nextLine(); + String[] args = saveFormatString.split("\\|", 4); + if (args.length < 3) { + Duke.addToResponse("Invalid task save string, task not added"); + continue; + } + + String taskType = args[0].strip(); + boolean marked = args[1].strip().equals("1") ? true : false; + String taskName = args[2].strip(); + if (taskName == "") { + Duke.addToResponse("Invalid task save string, task not added"); + continue; + } + + LocalDate date = null; + if (args.length >= 4) { + try { + date = LocalDate.parse(args[3].strip()); + } catch (DateTimeParseException e) { + Duke.addToResponse("Invalid date in task save string, task not added"); + } + } + + switch (taskType) { + case ToDo.TASK_TYPE_CHARACTER: + TaskList.addToList(new ToDo(taskName, marked)); + break; + case Deadline.TASK_TYPE_CHARACTER: + if (date != null) { + TaskList.addToList(new Deadline(taskName, marked, date)); + } + break; + case Event.TASK_TYPE_CHARACTER: + if (date != null) { + TaskList.addToList(new Event(taskName, marked, date)); + } + break; + default: + } + } + scanner.close(); + } + + /** + * Attempts to save tasks from TaskList to the hard disk. + * Creates the save file and directory if missing. + * + * @throws IOException If the save file or file path could not be accessed. + */ + public static void saveData() throws IOException { + Path parentDir = Paths.get(DATA_FILE_PATH); + if (!Files.exists(parentDir)) { + Files.createDirectories(parentDir); + } + + File dataFile = new File(Paths.get(parentDir.toString(), DATA_FILE_NAME).toString()); + if (!dataFile.exists()) { + dataFile.createNewFile(); + } + + FileWriter dataFileWriter = new FileWriter(dataFile); + + try { + for (String saveString: TaskList.getTasksSaveStrings()) { + dataFileWriter.write(saveString + "\n"); + } + + dataFileWriter.close(); + } catch (IOException e) { + Duke.addToResponse("Error: Failed to save tasks"); + } + } +} diff --git a/src/main/java/duke/Task.java b/src/main/java/duke/Task.java new file mode 100644 index 0000000000..31bc359cee --- /dev/null +++ b/src/main/java/duke/Task.java @@ -0,0 +1,63 @@ +package duke; + +/** + * A class used to represent a task. A task has a name and completion status. + */ +public abstract class Task { + protected static final String MARK_CHARACTER = "X"; + + protected String taskName = ""; + protected boolean isDone = false; + + /** + * The constructor for a Task. + * + * @param taskName A string that is the name of the task. + * @param isDone A boolean that represents whether this task is complete. + */ + public Task(String taskName, boolean isDone) { + this.taskName = taskName; + this.isDone = isDone; + } + + /** + * Gets the name of the task. + * + * @return A string that is name of the task. + */ + public String getTaskName() { + return this.taskName; + } + + /** + * Marks the task as done. + */ + public void mark() { + this.isDone = true; + } + + /** + * Marks the task as not done. + */ + public void unmark() { + this.isDone = false; + } + + /** + * Generates a string that can be used to reconstruct the task. + * Intended to be used by Storage to save and load tasks to the hard disk. + * + * @return A string that can be used to reconstruct the task. + */ + public abstract String toSaveFormatString(); + + /** + * Generates a string representation of the task meant for display. + * + * @return A string representation of the task meant for display. + */ + @Override + public String toString() { + return String.format("[%s] %s", isDone ? MARK_CHARACTER : " ", this.taskName); + } +} diff --git a/src/main/java/duke/TaskList.java b/src/main/java/duke/TaskList.java new file mode 100644 index 0000000000..91d3f2423c --- /dev/null +++ b/src/main/java/duke/TaskList.java @@ -0,0 +1,268 @@ +package duke; + +import java.time.LocalDate; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * A class that represents the task list and handles add/delete/update operations. + */ +public class TaskList { + private static List tasks = new ArrayList<>(); + + /** + * Gets the total number of tasks in the task list. + * + * @return The total number of tasks in the task list. + */ + public static int getTaskCount() { + return tasks.size(); + } + + /** + * Generates a list of string representations of tasks meant for display. + * Intended to be used by Ui for displaying all tasks. + * + * @return A list of strings that represent task information to be displayed. + */ + public static List getTasksStrings() { + ArrayList tasksStrings = new ArrayList<>(); + + for (int i = 0; i < tasks.size(); i++) { + tasksStrings.add(tasks.get(i).toString()); + } + + return tasksStrings; + } + + /** + * Adds the given task to the list. + * + * @param task The task to add to the list. + * @return The task that was added. + */ + public static Task addToList(Task task) { + assert task != null + : "a null Task should not be added to the list"; + + tasks.add(task); + + return task; + } + + /** + * Creates a ToDo task from the given argument string. + * + * @param args The argument string to be parsed. + * @return The task that was added. + */ + public static Task addToDo(String args) throws DukeException { + String name = args.strip(); + if (name == "") { + throw new DukeException("Failed to create todo: No task name given\n" + + "Command format: todo [taskName]"); + } + + return addToList(new ToDo(name, false)); + } + + /** + * Creates a Deadline task from the given argument string. + * + * @param args The argument string to be parsed. + * @return The task that was added. + */ + public static Task addDeadline(String args) throws DukeException { + String[] argsArr = args.split("/by", 2); + if (argsArr.length < 2) { + throw new DukeException("Failed to create deadline: Invalid number of arguments\n" + + "Command format: deadline [taskName] /by [date]"); + } + + String name = argsArr[0].strip(); + String dateStr = argsArr[1].strip(); + + if (name == "") { + throw new DukeException("Failed to create deadline: No task name given\n" + + "Command format: deadline [taskName] /by [date]"); + } + + if (dateStr == "") { + throw new DukeException("Failed to create deadline: No date given\n" + + "Command format: deadline [taskName] /by [date]"); + } + + LocalDate date; + try { + date = LocalDate.parse(dateStr); + } catch (DateTimeParseException e) { + throw new DukeException("Failed to create deadline: Invalid date given\n" + + "Command format: deadline [taskName] /by [date]"); + } + + return addToList(new Deadline(name, false, date)); + } + + /** + * Creates an Event task from the given argument string. + * + * @param args The argument string to be parsed. + * @return The task that was added. + */ + public static Task addEvent(String args) throws DukeException { + String[] argsArr = args.split("/at", 2); + if (argsArr.length < 2) { + throw new DukeException("Failed to create event: Invalid number of arguments\n" + + "Command format: event [taskName] /at [date]"); + } + + String name = argsArr[0].strip(); + String dateStr = argsArr[1].strip(); + + if (name == "") { + throw new DukeException("Failed to create event: No task name given\n" + + "Command format: event [taskName] /at [date]"); + } + + if (dateStr == "") { + throw new DukeException("Failed to create event: No date given\n" + + "Command format: event [taskName] /at [date]"); + } + + LocalDate date; + try { + date = LocalDate.parse(dateStr); + } catch (DateTimeParseException e) { + throw new DukeException("Failed to create event: Invalid date given\n" + + "Command format: event [taskName] /at [date]"); + } + + return addToList(new Event(name, false, date)); + } + + /** + * Marks/unmarks a task based on the given task index string. + * + * @param isDone Whether to mark/unmark the task. + * @param indexString The index of the task to mark/unmark. + * @return The task that was marked/unmarked. + * @throws DukeException If indexString is not an integer or out of range. + */ + public static Task markTask(boolean isDone, String indexString) throws DukeException { + int index; + try { + index = Integer.parseInt(indexString.strip()); + } catch (NumberFormatException e) { + throw new DukeException("Mark failed, invalid index\n" + + String.format("Command format: %s [index]", isDone ? "mark" : "unmark")); + } + index--; + + if (index < 0 || index >= tasks.size()) { + throw new DukeException("Mark failed, index out of range\n" + + String.format("Command format: %s [index]", isDone ? "mark" : "unmark")); + } + + Task task = tasks.get(index); + if (isDone) { + task.mark(); + } else { + task.unmark(); + } + + return task; + } + + /** + * Deletes a task based on the given task index string. + * + * @param indexString The index of the task to delete. + * @return The task that was deleted. + * @throws DukeException If indexString is not an integer or out of range. + */ + public static Task deleteTask(String indexString) throws DukeException { + int index; + try { + index = Integer.parseInt(indexString.strip()); + } catch (NumberFormatException e) { + throw new DukeException("Delete failed, invalid index\n" + + "Command format: delete [index]"); + } + index--; + + if (index < 0 || index >= tasks.size()) { + throw new DukeException("Delete failed, index out of range\n" + + "Command format: delete [index]"); + } + + Task task = tasks.get(index); + tasks.remove(index); + + return task; + } + + /** + * Searches for tasks based on the given search string, and returns a list of tasks found. + * If the taskName of a task contains the search string, it is added to the search results. + * + * @param searchString The string to be used to search for tasks. + * @return The list of tasks found in the search. + */ + public static List searchTasks(String searchString) { + searchString = searchString.strip(); + ArrayList foundTasks = new ArrayList<>(); + + for (Task task: tasks) { + if (task.getTaskName().contains(searchString)) { + foundTasks.add(task); + } + } + + return foundTasks; + } + + /** + * Converts all tasks to their save string format and returns it as a list. + * Intended to be used by Storage to save tasks. + * + * @return A list of strings which can be used to reconstruct tasks. + */ + public static List getTasksSaveStrings() { + ArrayList saveStrings = new ArrayList<>(); + + for (Task task: tasks) { + saveStrings.add(task.toSaveFormatString()); + } + + return saveStrings; + } + + /** + * Removes all tasks from the task list. + */ + public static void clearTaskList() { + tasks.clear(); + } + + /** + * Sorts all tasks by name in ascending or descending order. + * + * @param order String that determines whether to sort in ascending or descending order. + * @return A string that describes whether the sort was in ascending or descending order. + */ + public static String sortTaskListByName(String order) throws DukeException { + switch (order.strip()) { + case "/a": + tasks.sort(Comparator.comparing(Task::getTaskName)); + return "ascending"; + case "/d": + tasks.sort((taskA, taskB) -> taskB.getTaskName().compareTo(taskA.getTaskName())); + return "descending"; + default: + throw new DukeException("Failed to sort: No sorting order specified\n" + + "Command format: sort [/a for ascending or /d for descending]"); + } + } +} diff --git a/src/main/java/duke/ToDo.java b/src/main/java/duke/ToDo.java new file mode 100644 index 0000000000..1e615f343a --- /dev/null +++ b/src/main/java/duke/ToDo.java @@ -0,0 +1,22 @@ +package duke; + +/** + * Class used to represent a ToDo type task that has no date. + */ +public class ToDo extends Task { + public static final String TASK_TYPE_CHARACTER = "T"; + + public ToDo(String taskName, boolean isDone) { + super(taskName, isDone); + } + + @Override + public String toSaveFormatString() { + return String.format("%s|%d|%s", TASK_TYPE_CHARACTER, isDone ? 1 : 0, taskName); + } + + @Override + public String toString() { + return String.format("[%s]%s", TASK_TYPE_CHARACTER, super.toString()); + } +} diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/Ui.java new file mode 100644 index 0000000000..1ff758de8a --- /dev/null +++ b/src/main/java/duke/Ui.java @@ -0,0 +1,131 @@ +package duke; + +import java.util.List; + +/** + * A class that handles the displaying of information to the user. + */ +public class Ui { + private static final String LOGO = + " ____ _ \n" + + "| _ \\ _ _| | _____ \n" + + "| | | | | | | |/ / _ \\\n" + + "| |_| | |_| | < __/\n" + + "|____/ \\__,_|_|\\_\\___|\n"; + private static final String BOT_NAME = "Duke"; + private static final int LINE_LENGTH = 35; + + /** + * Displays the text to be shown on starting up Duke. + */ + public static void displayStartUpText() { + Duke.addToResponse("Hello from\n" + LOGO); + + generateLine(); + displayTasks(); + generateLine(); + + displayGreeting(); + } + + /** + * Displays the text to be shown when the user greets Duke with hello. + * This is also used in displayStartUpText. + */ + public static void displayGreeting() { + Duke.addToResponse(String.format("Hello, I'm %s\n", BOT_NAME)); + Duke.addToResponse(String.format("What can I do for you?\n")); + } + + /** + * Displays a line of length LINE_LENGTH. + */ + public static void generateLine() { + Duke.addToResponse(String.format("%" + LINE_LENGTH + "s", "").replace(" ", "-") + "\n"); + } + + /** + * Displays the text to be shown when the user exits the program. + */ + public static void displayExitText() { + Duke.addToResponse("Goodbye.\n"); + } + + /** + * Displays the text to be shown when the user adds a task to the list. + * + * @param task The task that was added to the list. + */ + public static void displayAddTaskMessage(Task task) { + Duke.addToResponse(String.format("Task added:\n%s\n", task)); + Duke.addToResponse(String.format("You now have %d task(s) in your list\n", TaskList.getTaskCount())); + } + + /** + * Displays the text to be shown when the user removes a task to the list. + * + * @param task The task that was removed from the list. + */ + public static void displayDeleteTaskMessage(Task task) { + Duke.addToResponse(String.format("Task deleted:\n%s\n", task)); + Duke.addToResponse(String.format("You now have %d task(s) in your list\n", TaskList.getTaskCount())); + } + + /** + * Displays the text to be shown when the user marks a task on the list. + * + * @param task The task that was marked on the list. + */ + public static void displayMarkTaskMessage(Task task, boolean isDone) { + Duke.addToResponse(String.format("Task marked as %s:\n%s\n", isDone ? "complete" : "incomplete", task)); + } + + /** + * Displays the text to be shown when the user searches for tasks. + * + * @param foundTasks The tasks that were found in the search. + * @param searchString The string that was used to perform the search. + */ + public static void displaySearchTasksMessage(List foundTasks, String searchString) { + Duke.addToResponse(String.format("Displaying search results for \"%s\":\n", + searchString, foundTasks.size())); + + for (int i = 0; i < foundTasks.size(); i++) { + Duke.addToResponse(String.format("%d. %s\n", i + 1, foundTasks.get(i))); + } + + Duke.addToResponse(String.format("\n%d task(s) found\n", foundTasks.size())); + } + + /** + * Displays all tasks in the list. + */ + public static void displayTasks() { + List tasksStrings = TaskList.getTasksStrings(); + + for (int i = 0; i < tasksStrings.size(); i++) { + Duke.addToResponse(String.format("%d. %s\n", i + 1, tasksStrings.get(i))); + } + + Duke.addToResponse(String.format("\nYou have %d task(s) in your list\n", TaskList.getTaskCount())); + } + + /** + * Displays the text to be shown when the user sorts the tasks by name + */ + public static void displaySortTasksByNameMessage(String order) { + Duke.addToResponse(String.format("Tasks sorted by name in %s order\n", order)); + displayTasks(); + } + + /** + * Displays the list of available commands + */ + public static void displayCommands() { + Duke.addToResponse("Available commands:\n\n"); + List commands = Parser.getCommandList(); + for (String command : commands) { + Duke.addToResponse(String.format("%s\n", command)); + } + } +} diff --git a/src/main/resources/images/DukeIcon.png b/src/main/resources/images/DukeIcon.png new file mode 100644 index 0000000000..53ef4b8095 Binary files /dev/null and b/src/main/resources/images/DukeIcon.png differ diff --git a/src/main/resources/images/UserIcon.png b/src/main/resources/images/UserIcon.png new file mode 100644 index 0000000000..1d69e859a6 Binary files /dev/null and b/src/main/resources/images/UserIcon.png differ diff --git a/src/main/resources/view/DialogueBox.fxml b/src/main/resources/view/DialogueBox.fxml new file mode 100644 index 0000000000..d9ffc92a4c --- /dev/null +++ b/src/main/resources/view/DialogueBox.fxml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..86cac0f44f --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + +