diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000000..81e70d052b --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,35 @@ +name: Java CI + +on: [push, pull_request] + +jobs: + build: + strategy: + matrix: + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + + steps: + - name: Set up repository + uses: actions/checkout@master + + - name: Set up repository + uses: actions/checkout@master + with: + ref: master + + - name: Merge to master + run: git checkout --progress --force ${{ github.sha }} + + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v1 + + - name: Setup JDK 11 + uses: actions/setup-java@v1 + with: + java-version: '11' + java-package: jdk+fx + + - name: Build and check with Gradle + run: ./gradlew check + diff --git a/.gitignore b/.gitignore index 2873e189e1..b8e62d3ba1 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT +/data diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..8f003c7b91 --- /dev/null +++ b/build.gradle @@ -0,0 +1,65 @@ +plugins { + id 'java' + id 'application' + id 'com.github.johnrengelman.shadow' version '7.1.2' + id 'checkstyle' + id 'org.openjfx.javafxplugin' version '0.0.13' +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.10.0' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0' + + // @@author lordidiot-reused + // Taken from https://github.com/nus-cs2103-AY2324S1/forum/issues/173 + def platforms = ["win", "linux", "mac"] + def javafxDependency = ["javafx-graphics", "javafx-controls", "javafx-fxml"] + + for (plt in platforms) { + for (dep in javafxDependency) { + runtimeOnly "org.openjfx:$dep:$javafx.version:$plt" + } + } + // @@author +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + +application { + mainClass.set("duke.Launcher") +} + +shadowJar { + archiveBaseName = "duke" + archiveClassifier = null + dependsOn("distZip", "distTar") +} + +run { + standardInput = System.in +} + +checkstyle { + toolVersion = '10.2' +} + +javafx { + version = "17.0.7" + modules = [ 'javafx.base', 'javafx.controls', 'javafx.fxml', 'javafx.graphics' ] +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..eb761a9b9a --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..39efb6e4ac --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/docs/README.md b/docs/README.md index 8077118ebe..9dbb54568f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,175 @@ -# User Guide +# Pong User Guide + +![Screenshot of Pong chatbot.](Ui.png) ## Features -### Feature-ABC +### List tasks (`list`) + +Lists all tasks. + +### Add todo (`todo`) + +Adds todo task. + +### Add deadline (`deadline`) + +Adds deadline task. + +### Add event (`event`) + +Adds event task. + +### Mark task (`mark`) + +Marks task as done. + +### Unmark task (`unmark`) -Description of the feature. +Marks task as **not** done. -### Feature-XYZ +### Delete task (`delete`) -Description of the feature. +Deletes task. + +### Exit application (`bye`) + +Exits Duke chatbot. ## Usage -### `Keyword` - Describe action +### `list` - Lists tasks + +Lists all tasks. -Describe the action and its outcome. +Format: + +`list` Example of usage: -`keyword (optional arguments)` +`list` + +Expected outcome: + +Notice that pong will output all tasks, their types and whether they are marked as done. + +``` + 1. [T][X] Prepare breakfast + 2. [D][ ] CS2103T tP UG (by: 2023-09-20 23:59) + 3. [D][ ] SEP Application (by: 2023-09-24 23:59) +``` + +### `todo` - Add todo + +Add todo task. + +Format: + +`todo ` + +Example of usage: + +`todo Prepare breakfast` Expected outcome: -Description of the outcome. +``` +[Added] [T][ ] Prepare breakfast +``` + +### `deadline` - Add deadline + +Add deadline task. + +Format: + +`deadline /by ` (date in `yyyy/MM/dd [HHmm]`) + +Example of usage: + +`deadline CS2103T tP UG /by 2023/09/20 2359` + +Expected outcome: ``` -expected output +[Added] [D][ ] CS2103T Lecture (by: 2023-09-20 23:59) ``` + +### `event` - Add event + +Add event task. + +Format: + +`event /from /to ` (dates in `yyyy/MM/dd [HHmm]`) + +Example of usage: + +`event CS2103T Lecture /from 2023/09/22 1600 /to 2023/09/22 1800` + +Expected outcome: + +``` +[Added] [E][ ] CS2103T Lecture (from: 2023-09-22 16:00, to: 2023-09-22 18:00) +``` + +### `mark` - Mark task + +Marks task as done. + +Format: + +`mark ` + +Example of usage: + +`mark 1` + +Expected outcome: + +``` +I've marked this task done. + [T][X] Prepare breakfast +``` + +### `unmark` - Unmark task + +Marks task as **not** done. + +Format: + +`unmark ` + +Example of usage: + +`unmark 1` + +Expected outcome: + +``` +I've marked this task not done. + [T][ ] Prepare breakfast +``` + +### `delete` - Deletes task + +Deletes task. + +Format: + +`delete ` + +Example of usage: + +`delete 1` + +Expected outcome: + +``` +[Deleted] [T][ ] Prepare breakfast +``` + +## Acknowledgements + +- Used some gradle fixes from [@woojiahao](https://github.com/woojiahao) written [here](https://github.com/nus-cs2103-AY2324S1/forum/issues/173) diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..47d72d98d8 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..033e24c4cd 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..66c01cfeba --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..fcb6fca147 --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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 POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# 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 ;; #( + MSYS* | 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# 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"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..6689b85bee --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@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=. +@rem This is normally unused +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% equ 0 goto execute + +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 execute + +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 + +: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 %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334cc..0000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} diff --git a/src/main/java/duke/DialogBox.java b/src/main/java/duke/DialogBox.java new file mode 100644 index 0000000000..1286339296 --- /dev/null +++ b/src/main/java/duke/DialogBox.java @@ -0,0 +1,61 @@ +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; + +/** + * 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(); + } + + 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); + } + + public static DialogBox getUserDialog(String text, Image img) { + return new DialogBox(text, img); + } + + public static DialogBox getDukeDialog(String text, Image img) { + var db = new DialogBox(text, img); + 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..18136dd03c --- /dev/null +++ b/src/main/java/duke/Duke.java @@ -0,0 +1,162 @@ +package duke; + +import java.time.LocalDateTime; +import java.util.List; + +import duke.task.Deadline; +import duke.task.Event; +import duke.task.Task; +import duke.task.TaskList; +import duke.task.TaskStorage; +import duke.task.Todo; +import duke.ui.Parser; +import duke.ui.Ui; + +/** + * Duke class handles main logic for the chatbot. + */ +public class Duke { + private final TaskList taskList; + private final Ui ui; + + /** + * Initialise Duke chatbot instance. + * @param ui Ui type that Duke will use. + * @throws DukeException + */ + public Duke(Ui ui) throws DukeException { + this.taskList = new TaskList(new TaskStorage()); + this.ui = ui; + } + + /** + * Passes input to duke to handle. + * @param input String input to handle. + * @return isRunning boolean to decide if Duke should continue. + * @throws DukeException If error encountered while handling input. + */ + public boolean handleInput(String input) throws DukeException { + Parser parser = Parser.from(input); + assert parser != null : "The variable 'parser' is null"; + + switch (parser.getCommand()) { + case "bye": + return false; + case "list": + listCommand(); + break; + case "mark": + markCommand(parser); + break; + case "unmark": + unmarkCommand(parser); + break; + case "delete": + deleteCommand(parser); + break; + case "todo": + todoCommand(parser); + break; + case "deadline": + deadlineCommand(parser); + break; + case "event": + eventCommand(parser); + break; + case "find": + findCommand(parser); + break; + default: + invalidCommand(parser); + break; + } + + return true; + } + + private void listCommand() { + ui.listTasks(taskList.getTasks()); + } + + private void markCommand(Parser parser) throws DukeException { + Task task = taskList.markTask(parser.getArgAsInt()); + ui.markTask(task); + } + + private void unmarkCommand(Parser parser) throws DukeException { + Task task = taskList.unmarkTask(parser.getArgAsInt()); + ui.unmarkTask(task); + } + + private void deleteCommand(Parser parser) throws DukeException { + Task task = taskList.deleteTask(parser.getArgAsInt()); + ui.deleteTask(task); + } + + private void todoCommand(Parser parser) throws DukeException { + String todoName = parser.getArg(); + if (todoName == null || todoName.equals("")) { + throw new DukeException("duke.Todo name cannot be empty"); + } + Task task = new Todo(todoName); + taskList.addTask(task); + ui.addTask(task); + } + + private void deadlineCommand(Parser parser) throws DukeException { + String deadlineName = parser.getArg(); + if (deadlineName == null || deadlineName.equals("")) { + throw new DukeException("duke.Deadline name cannot be empty"); + } + + LocalDateTime deadline; + deadline = parser.getOptArgAsDateTime("by"); + + if (deadline == null) { + throw new DukeException("Use /by to specify deadline date (yyyy/MM/dd [HHmm])"); + } + + Task task = new Deadline(deadlineName, deadline); + taskList.addTask(task); + ui.addTask(task); + } + + private void eventCommand(Parser parser) throws DukeException { + String eventName = parser.getArg(); + if (eventName == null || eventName.equals("")) { + throw new DukeException("duke.Deadline name cannot be empty"); + } + + LocalDateTime from; + LocalDateTime to; + from = parser.getOptArgAsDateTime("from"); + to = parser.getOptArgAsDateTime("to"); + + if (from == null || to == null) { + throw new DukeException("Use /from and /to to specify event duration (yyyy/MM/dd [HHmm])"); + } + + Task task = new Event(eventName, from, to); + taskList.addTask(task); + ui.addTask(task); + } + + private void findCommand(Parser parser) { + String search = parser.getArg(); + List tasks = taskList.findTasks(search); + ui.listTasks(tasks); + } + + private void invalidCommand(Parser parser) { + ui.invalidCommand(parser.getCommand()); + } + + + /** + * Gracefully handle exception thrown by Duke. + * @param e Exception thrown by Duke. + */ + public void handleException(DukeException e) { + this.ui.printException(e); + } +} diff --git a/src/main/java/duke/DukeException.java b/src/main/java/duke/DukeException.java new file mode 100644 index 0000000000..d9d8c7f458 --- /dev/null +++ b/src/main/java/duke/DukeException.java @@ -0,0 +1,15 @@ +package duke; + +/** + * DukeException encapsulates custom exceptions thrown + * by the Duke project. + */ +public class DukeException extends Exception { + /** + * Constructor for DukeException. + * @param errorMessage Custom error message. + */ + public DukeException(String errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java new file mode 100644 index 0000000000..35c362cffb --- /dev/null +++ b/src/main/java/duke/Launcher.java @@ -0,0 +1,16 @@ +package duke; + +import javafx.application.Application; + +/** + * Launcher class to launch GUI main. + */ +public class Launcher { + /** + * Entrypoint for Duke GUI application. + * @param args Command-line arguments (unused). + */ + public static void main(String[] args) { + Application.launch(Main.class, args); + } +} diff --git a/src/main/java/duke/Main.java b/src/main/java/duke/Main.java new file mode 100644 index 0000000000..9d7d407dfc --- /dev/null +++ b/src/main/java/duke/Main.java @@ -0,0 +1,44 @@ +package duke; + +import java.io.IOException; + +import duke.ui.TextUi; +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + +/** + * A GUI for Duke using FXML. + */ +public class Main extends Application { + + private final Duke duke; + + private final TextUi textUi; + + /** + * Main constructor, sets up dependencies. + * @throws DukeException + */ + public Main() throws DukeException { + this.textUi = new TextUi("Pong"); + this.duke = new Duke(this.textUi); + } + + @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().setDuke(duke); + fxmlLoader.getController().setTextUi(textUi); + 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..c9ec20d0b6 --- /dev/null +++ b/src/main/java/duke/MainWindow.java @@ -0,0 +1,74 @@ +package duke; + +import duke.ui.TextUi; +import javafx.application.Platform; +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 Duke duke; + private TextUi textUi; + + private final Image userImage = new Image(this.getClass().getResourceAsStream("/images/DaUser.png")); + private final Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png")); + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + } + + public void setDuke(Duke d) { + duke = d; + } + + public void setTextUi(TextUi textUi) { + this.textUi = textUi; + } + + /** + * 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() { + boolean isRunning; + String input = userInput.getText(); + + try { + isRunning = duke.handleInput(input); + if (!isRunning) { + exit(); + } + } catch (DukeException e) { + duke.handleException(e); + } + + String response = textUi.getTextOutput(); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(input, userImage), + DialogBox.getDukeDialog(response, dukeImage) + ); + userInput.clear(); + } + + private void exit() { + Platform.exit(); + } +} diff --git a/src/main/java/duke/task/Deadline.java b/src/main/java/duke/task/Deadline.java new file mode 100644 index 0000000000..08aecb79f8 --- /dev/null +++ b/src/main/java/duke/task/Deadline.java @@ -0,0 +1,38 @@ +package duke.task; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Encapsulation of a Deadline task. + */ +public class Deadline extends Task { + private final LocalDateTime deadline; + + /** + * Deadline constructor. + * @param deadlineName Name of deadline. + * @param deadline Time and date of the deadline. + */ + public Deadline(String deadlineName, LocalDateTime deadline) { + super(deadlineName); + this.deadline = deadline; + } + + @Override + public String toString() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + return String.format("[D]%s (by: %s)", super.toString(), this.deadline.format(formatter)); + } + + @Override + public String serialize() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HHmm"); + return String.format( + "deadline %s /by %s%s", + getTaskName(), + this.deadline.format(formatter), + this.isDone() ? " /done" : "" + ); + } +} diff --git a/src/main/java/duke/task/Event.java b/src/main/java/duke/task/Event.java new file mode 100644 index 0000000000..10afc87e93 --- /dev/null +++ b/src/main/java/duke/task/Event.java @@ -0,0 +1,48 @@ +package duke.task; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Encapsulation of an Event task. + */ +public class Event extends Task { + private final LocalDateTime from; + + private final LocalDateTime to; + + /** + * Constructor for Event. + * @param eventName Name of event. + * @param from Date and time of event start. + * @param to Date and time of event end. + */ + public Event(String eventName, LocalDateTime from, LocalDateTime to) { + super(eventName); + this.from = from; + this.to = to; + } + + @Override + public String toString() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + return String.format( + "[E]%s (from: %s, to: %s)", + super.toString(), + this.from.format(formatter), + this.to.format(formatter) + ); + } + + @Override + public String serialize() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HHmm"); + return String.format( + "event %s /from %s /to %s%s", + getTaskName(), + this.from.format(formatter), + this.to.format(formatter), + this.isDone() ? " /done" : "" + ); + } +} diff --git a/src/main/java/duke/task/Task.java b/src/main/java/duke/task/Task.java new file mode 100644 index 0000000000..6a2dc919fa --- /dev/null +++ b/src/main/java/duke/task/Task.java @@ -0,0 +1,61 @@ +package duke.task; + +/** + * Abstract class encapsulating Tasks. + */ +public abstract class Task { + private boolean isDone; + private final String taskName; + + /** + * Constructor for a Task. + * @param taskName Name of task. + */ + public Task(String taskName) { + this.isDone = false; + this.taskName = taskName; + } + + /** + * Check if task is marked done. + * @return true if task is marked done else false. + */ + public boolean isDone() { + return this.isDone; + } + + /** + * Mark task as done. + */ + public void markDone() { + this.isDone = true; + } + + /** + * Mark task as not done. + */ + public void unmarkDone() { + this.isDone = false; + } + + /** + * Get name of task. + * @return Name of task. + */ + public String getTaskName() { + return this.taskName; + } + + @Override + public String toString() { + return String.format("[%c] %s", (this.isDone ? 'X' : ' '), this.taskName); + } + + /** + * Convert data contained by the task into a + * string format to be stored and deserialized back + * into a Task object in the future. + * @return Serialized string representation of Task instance. + */ + public abstract String serialize(); +} diff --git a/src/main/java/duke/task/TaskList.java b/src/main/java/duke/task/TaskList.java new file mode 100644 index 0000000000..778e97f347 --- /dev/null +++ b/src/main/java/duke/task/TaskList.java @@ -0,0 +1,110 @@ +package duke.task; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import duke.DukeException; + +/** + * Helper class encapsulating list of tasks + * provides methods for basic operations as well. + */ +public class TaskList { + private final ArrayList tasks; + + private final TaskStorage storage; + + /** + * Initialises duke.TaskList and restores existing tasks + * from duke.TaskStorage provided as argument. + * @param storage duke.TaskStorage to use for restore/save of tasks. + * @throws DukeException If an issue occurs during restore of tasks. + */ + public TaskList(TaskStorage storage) throws DukeException { + this.storage = storage; + this.tasks = storage.loadExistingTasks(); + } + + /** + * Adds task. + * @param task duke.Task to add. + */ + public void addTask(Task task) throws DukeException { + this.tasks.add(task); + storeTasks(); + } + + /** + * Deletes task by index. + * @param index Index of task to delete. + * @return duke.Task deleted. + */ + public Task deleteTask(int index) throws DukeException { + index -= 1; + + if (index < 0 || index >= this.tasks.size()) { + throw new DukeException("Index out of bounds"); + } + + Task task = this.tasks.remove(index); + storeTasks(); + return task; + } + + /** + * Marks task as done. + * @param index Index of task to mark. + */ + public Task markTask(int index) throws DukeException { + index -= 1; + + if (index < 0 || index >= this.tasks.size()) { + throw new DukeException("Index out of bounds"); + } + + Task task = this.tasks.get(index); + task.markDone(); + storeTasks(); + return task; + } + + /** + * Marks task as not done. + * @param index Index of task to unmark. + */ + public Task unmarkTask(int index) throws DukeException { + index -= 1; + + if (index < 0 || index >= this.tasks.size()) { + throw new DukeException("Index out of bounds"); + } + + Task task = this.tasks.get(index); + task.unmarkDone(); + storeTasks(); + return task; + } + + /** + * Get all tasks. + * @return All tasks. + */ + public List getTasks() { + return this.tasks; + } + + /** + * Find tasks with names that match search term. + * @return List of tasks that match search. + */ + public List findTasks(String search) { + return this.tasks.stream() + .filter(t -> t.getTaskName().toLowerCase().contains(search.toLowerCase())) + .collect(Collectors.toList()); + } + + private void storeTasks() throws DukeException { + this.storage.storeTasks(this.tasks); + } +} diff --git a/src/main/java/duke/task/TaskStorage.java b/src/main/java/duke/task/TaskStorage.java new file mode 100644 index 0000000000..9b84ab4361 --- /dev/null +++ b/src/main/java/duke/task/TaskStorage.java @@ -0,0 +1,110 @@ +package duke.task; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import duke.DukeException; +import duke.ui.Parser; + +/** + * Handles storage of tasks. + * Uses text file as storage method. + */ +public class TaskStorage { + public static final String DEFAULT_PATH = "./data/duke.txt"; + + private final File file; + + /** + * TaskStorage constructor, uses default path. + */ + public TaskStorage() { + this.file = new File(DEFAULT_PATH); + } + + /** + * Load existing tasks from text file. + * @return ArrayList of Tasks loaded + * @throws DukeException Any errors encountered while loading + */ + public ArrayList loadExistingTasks() throws DukeException { + try { + if (!this.file.exists()) { + this.file.getParentFile().mkdirs(); + this.file.createNewFile(); + return new ArrayList<>(); + } + + ArrayList tasks = new ArrayList<>(); + FileReader fileReader = new FileReader(this.file); + BufferedReader bufferedReader = new BufferedReader(fileReader); + + // Format: ||[|...] + Parser parser; + String line; + while ((line = bufferedReader.readLine()) != null) { + Task task; + parser = Parser.from(line); + + switch(parser.getCommand()) { + case "todo": + task = new Todo(parser.getArg()); + break; + case "deadline": + task = new Deadline( + parser.getArg(), + parser.getOptArgAsDateTime("by") + ); + break; + case "event": + task = new Event( + parser.getArg(), + parser.getOptArgAsDateTime("from"), + parser.getOptArgAsDateTime("to") + ); + break; + default: + continue; + } + + if (parser.getOptArg("done") != null) { + task.markDone(); + } + + tasks.add(task); + } + fileReader.close(); + + return tasks; + } catch (IOException e) { + throw new DukeException("Error while loading tasks: " + e.getMessage()); + } + } + + + /** + * Stores serialized tasks in text file. + * @param tasks Tasks to store + * @throws DukeException Any errors encountered while storing + */ + public void storeTasks(List tasks) throws DukeException { + String serialized = tasks.stream() + .map(Task::serialize) + .collect(Collectors.joining("\n")); + + try { + FileWriter fileWriter = new FileWriter(this.file); + fileWriter.write(serialized); + fileWriter.flush(); + fileWriter.close(); + } catch (IOException e) { + throw new DukeException("Error while storing tasks: " + e.getMessage()); + } + } +} diff --git a/src/main/java/duke/task/Todo.java b/src/main/java/duke/task/Todo.java new file mode 100644 index 0000000000..39948d429c --- /dev/null +++ b/src/main/java/duke/task/Todo.java @@ -0,0 +1,28 @@ +package duke.task; + +/** + * Encapsulation of a Todo task. + */ +public class Todo extends Task { + /** + * Constructor for Todo. + * @param todoName Name of todo. + */ + public Todo(String todoName) { + super(todoName); + } + + @Override + public String toString() { + return String.format("[T]%s", super.toString()); + } + + @Override + public String serialize() { + return String.format( + "todo %s%s", + getTaskName(), + this.isDone() ? " /done" : "" + ); + } +} diff --git a/src/main/java/duke/ui/CliUi.java b/src/main/java/duke/ui/CliUi.java new file mode 100644 index 0000000000..c93299848c --- /dev/null +++ b/src/main/java/duke/ui/CliUi.java @@ -0,0 +1,88 @@ +package duke.ui; + +import java.util.List; +import java.util.Scanner; + +import duke.task.Task; + +/** + * Helper class for user interface, handles + * user input and outputting to the user. + */ +public class CliUi implements Ui { + private static final String DOTTED_LINE = "____________________________________________________________"; + + private final String name; + + private final Scanner scanner; + + /** + * Initialise Ui handler. + * @param name Name of chatbot. + */ + public CliUi(String name) { + this.name = name; + this.scanner = new Scanner(System.in); + } + + @Override + public void init() { + System.out.println(CliUi.DOTTED_LINE); + System.out.printf("Hello! I'm %s\n", this.name); + System.out.println("What can I do for you?"); + System.out.println(CliUi.DOTTED_LINE); + } + + @Override + public String getInput() { + System.out.print("You: "); + return scanner.nextLine(); + } + + @Override + public void printException(Exception e) { + System.out.printf("[!] %s\n", e.getMessage()); + } + + @Override + public void exit() { + System.out.println(CliUi.DOTTED_LINE); + System.out.println("Bye. Hope to see you again soon!"); + System.out.println(CliUi.DOTTED_LINE); + } + + @Override + public void addTask(Task task) { + System.out.printf("%s: [Added] %s\n", this.name, task); + } + + @Override + public void deleteTask(Task task) { + System.out.printf("%s: [Deleted] %s\n", this.name, task); + } + + @Override + public void markTask(Task task) { + System.out.printf("%s: I've marked this task as done.\n", this.name); + System.out.printf(" %s\n", task); + } + + @Override + public void unmarkTask(Task task) { + System.out.printf("%s: I've marked this task as not done.\n", this.name); + System.out.printf(" %s\n", task); + } + + @Override + public void listTasks(List tasks) { + System.out.printf("%s: \n", this.name); + for (int i = 0; i < tasks.size(); i++) { + System.out.printf(" %d. %s\n", i + 1, tasks.get(i)); + } + } + + @Override + public void invalidCommand(String command) { + System.out.printf("%s: Invalid command (%s)\n", this.name, command); + } +} diff --git a/src/main/java/duke/ui/Parser.java b/src/main/java/duke/ui/Parser.java new file mode 100644 index 0000000000..13f0780393 --- /dev/null +++ b/src/main/java/duke/ui/Parser.java @@ -0,0 +1,165 @@ +package duke.ui; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; +import java.util.HashMap; + +import duke.DukeException; + +/** + * Helper class to parse user input. + */ +public class Parser { + private static final String DELIMITER_REGEX = " (?=/\\w+)"; + + private final String command; + private final String argument; + + private final HashMap optionalArgs; + + private Parser(String command) { + this.command = command; + this.argument = null; + this.optionalArgs = null; + } + + private Parser(String command, String argument, HashMap optionalArgs) { + this.command = command; + this.argument = argument; + this.optionalArgs = optionalArgs; + } + + /** + * Factory method to parses line of input and + * extract task type, name and optional args. + * @param line User input. + * @return duke.Parser instance with parsed information. + * @throws DukeException If input does not match expected format. + */ + public static Parser from(String line) throws DukeException { + String taskName; + String taskType; + HashMap optionalArgs; + + int firstSpace = line.indexOf(' '); + + // No arguments (bye/list/...) + if (firstSpace == -1) { + return new Parser(line); + } + + taskType = line.substring(0, firstSpace); + String remainder = line.substring(firstSpace + 1); + String[] parts = remainder.split(DELIMITER_REGEX); + + if (parts.length < 1 || parts[0].equals("")) { + throw new DukeException("Invalid command. Expected format: [...]."); + } + + taskName = parts[0]; + optionalArgs = new HashMap<>(); + for (int i = 1; i < parts.length; i++) { + String k; + String v; + String part = parts[i]; + int space = part.indexOf(' '); + + if (space == -1) { + k = part.substring(1); + v = ""; + } else { + k = part.substring(1, space); + v = part.substring(space + 1); + } + + optionalArgs.put(k, v); + } + + return new Parser(taskType, taskName, optionalArgs); + } + + /** + * Get command. + * @return Command name. + */ + public String getCommand() { + return this.command; + } + + /** + * Get first argument. + * @return First argument. + */ + public String getArg() { + return this.argument; + } + + /** + * Get first argument as integer. + * @return First argument cast to integer. + * @throws DukeException If argument cannot be parsed to integer. + */ + public int getArgAsInt() throws DukeException { + if (this.argument == null) { + throw new DukeException("No integer argument provided"); + } + + try { + return Integer.parseInt(this.argument); + } catch (NumberFormatException e) { + throw new DukeException(String.format("Invalid integer argument: %s", this.argument)); + } + } + + /** + * Get optional argument value. + * @param argName Name of argument to get. + * @return Value of optional argument or null if not present. + */ + public String getOptArg(String argName) { + if (this.optionalArgs == null || !this.optionalArgs.containsKey(argName)) { + return null; + } + + return this.optionalArgs.get(argName); + } + + /** + * Get optional argument value as LocalDateTime. + * @param argName Name of argument to get. + * @return Value of optional argument as LocalDateTime or null if not present. + * @throws DukeException If argument value cannot be parsed as LocalDateTime. + */ + public LocalDateTime getOptArgAsDateTime(String argName) throws DukeException { + String arg = getOptArg(argName); + + if (arg == null) { + return null; + } + + SimpleDateFormat dateFormat; + + if (arg.length() == 10) { + dateFormat = new SimpleDateFormat("yyyy/MM/dd"); + } else if (arg.length() == 15) { + dateFormat = new SimpleDateFormat("yyyy/MM/dd HHmm"); + } else { + throw new DukeException(String.format("Invalid date (%s), should be yyyy/MM/dd HHmm", arg)); + } + + Date parsedDate; + try { + parsedDate = dateFormat.parse(arg); + } catch (ParseException e) { + throw new DukeException(String.format("Invalid date (%s), should be yyyy/MM/dd HHmm", arg)); + } + + return parsedDate + .toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDateTime(); + } +} diff --git a/src/main/java/duke/ui/TextUi.java b/src/main/java/duke/ui/TextUi.java new file mode 100644 index 0000000000..24b0b3efdb --- /dev/null +++ b/src/main/java/duke/ui/TextUi.java @@ -0,0 +1,94 @@ +package duke.ui; + +import java.util.List; + +import duke.task.Task; + +/** + * Helper class for user interface, handles + * user input and outputting to the user. + */ +public class TextUi implements Ui { + + private static final String DOTTED_LINE = "____________________________________________________________"; + private final StringBuilder buffer; + + private String name; + + /** + * Initialise Ui handler. + * @param name Name of chatbot. + */ + public TextUi(String name) { + this.name = name; + this.buffer = new StringBuilder(); + } + + /** + * Get accumulated output from the UI. + * @return Text output accumulated so far. + */ + public String getTextOutput() { + String out = this.buffer.toString(); + this.buffer.delete(0, this.buffer.length()); + return out; + } + + @Override + public void init() { + // Does nothing for TextUi + } + + @Override + public String getInput() { + // Does nothing for TextUi + return ""; + } + + @Override + public void printException(Exception e) { + this.buffer.append(String.format("[!] %s\n", e.getMessage())); + } + + @Override + public void exit() { + this.buffer.append(TextUi.DOTTED_LINE); + this.buffer.append("Bye. Hope to see you again soon!\n"); + this.buffer.append(TextUi.DOTTED_LINE); + } + + @Override + public void addTask(Task task) { + this.buffer.append(String.format("%s: [Added] %s\n", this.name, task)); + } + + @Override + public void deleteTask(Task task) { + this.buffer.append(String.format("%s: [Deleted] %s\n", this.name, task)); + } + + @Override + public void markTask(Task task) { + this.buffer.append(String.format("%s: I've marked this task as done.\n", this.name)); + this.buffer.append(String.format(" %s\n", task)); + } + + @Override + public void unmarkTask(Task task) { + this.buffer.append(String.format("%s: I've marked this task as not done.\n", this.name)); + this.buffer.append(String.format(" %s\n", task)); + } + + @Override + public void listTasks(List tasks) { + this.buffer.append(String.format("%s: \n", this.name)); + for (int i = 0; i < tasks.size(); i++) { + this.buffer.append(String.format(" %d. %s\n", i + 1, tasks.get(i))); + } + } + + @Override + public void invalidCommand(String command) { + this.buffer.append(String.format("%s: Invalid command (%s)\n", this.name, command)); + } +} diff --git a/src/main/java/duke/ui/Ui.java b/src/main/java/duke/ui/Ui.java new file mode 100644 index 0000000000..2bea34fda3 --- /dev/null +++ b/src/main/java/duke/ui/Ui.java @@ -0,0 +1,68 @@ +package duke.ui; + +import java.util.List; + +import duke.task.Task; + +/** + * Ui interface that the various types of Ui should implement. + */ +public interface Ui { + /** + * Initialises Ui. + */ + void init(); + + /** + * Prompts user and gets user input. + * @return String user input. + */ + String getInput(); + + /** + * Prints exception to user. + * @param e Exception to display. + */ + void printException(Exception e); + + /** + * Prints exit message to user. + */ + void exit(); + + /** + * Prints add task message to user. + * @param task duke.Task added. + */ + void addTask(Task task); + + /** + * Prints delete task message to user. + * @param task duke.Task deleted. + */ + void deleteTask(Task task); + + /** + * Prints mark task message to user. + * @param task duke.Task marked. + */ + void markTask(Task task); + + /** + * Prints unmark task message to user. + * @param task duke.Task unmarked. + */ + void unmarkTask(Task task); + + /** + * Lists all current tasks to user. + * @param tasks Tasks to list. + */ + void listTasks(List tasks); + + /** + * Prints invalid command message. + * @param command Command used. + */ + void invalidCommand(String command); +} diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png new file mode 100644 index 0000000000..d893658717 Binary files /dev/null and b/src/main/resources/images/DaDuke.png differ diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png new file mode 100644 index 0000000000..3c82f45461 Binary files /dev/null and b/src/main/resources/images/DaUser.png differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..176e51bcd5 --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..9f29e1c72b --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + +