diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..00a51aff5e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf + diff --git a/.gitignore b/.gitignore index f69985ef1f..0d336bef5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +*.class +/duke/ +/duketest/ + # IDEA files /.idea/ /out/ @@ -14,4 +18,9 @@ src/main/resources/docs/ bin/ /text-ui-test/ACTUAL.txt -text-ui-test/EXPECTED-UNIX.TXT +text-ui-test/EXPECTED-UNIX.txt +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..aedf59112f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ +"editor.detectIndentation": false, +"java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..11b0bf8235 --- /dev/null +++ b/build.gradle @@ -0,0 +1,48 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This is a general purpose Gradle build. + * Learn more about Gradle by exploring our samples at https://docs.gradle.org/7.3.3/samples + */ + +plugins { + id 'java' + id 'application' + id 'checkstyle' + id 'com.github.johnrengelman.shadow' version '5.1.0' +} + +application { + mainClassName "duke.Launcher" +} + +checkstyle { + toolVersion = '8.29' +} + +test { + useJUnitPlatform() +} + +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' +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..b1e659a3f8 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,403 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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..ee2c5b72d1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,157 @@ -# User Guide +# Chaterpillar User Guide -## Features +Welcome to Chaterpillar, a todo list and contacts management app that lives on your desktop. <3 -### Feature-ABC + -Description of the feature. +
-### Feature-XYZ +## Getting Started -Description of the feature. +1. Make sure you have Java 11 installed +2. Download the jar file `ip-all.jar` +3. Open your command line and run `java -jar path/to/ip-all.jar` -## Usage +
-### `Keyword` - Describe action +## Features -Describe the action and its outcome. +### Todo List +Chaterpillar helps you maintain a todo list which is stored on your hard drive and will be loaded each time you open the app. -Example of usage: +#### Add Task +Chaterpillar allows you to add 3 different types of tasks to your todo list. -`keyword (optional arguments)` +##### Todo [T] +Create a "todo" if your task is not time-sensitive. +###### Command +`todo ` +###### Example +`todo edit novel draft 11` +###### Example Result +"I've added the task: [T][ ] edit novel draft 11" -Expected outcome: +
-Description of the outcome. +##### Deadline [D] +Create a "deadline" if your task must be completed before a certain date time. +###### Command +`deadline /by ` +###### Example +`deadline CS2103 IP /by 2022-03-20 23:59` +###### Example Result +"I've added the task: [D][ ] CS2103 IP (by: 2022-03-20 23:59)" -``` -expected output -``` +
+ +##### Event [E] +Create an "event" if your task must be completed at a specific time. +###### Command +`event /at ` +###### Example +`event birthday party /at 2022-04-01 17:30` +###### Example Result +"I've added the task: [E][ ] birthday party (at: 2022-04-01 17:30)" + +
+ +#### List Tasks +Display an ordered list of all your tasks. +##### Command +`event /at ` + +
+ +#### Find +Shows the list of tasks that contains a specified string. +##### Command +`find ` +##### Example +`find homework` + +### Exit +Closes the Chaterpillar app. +#### Command +`bye` + +
+ +#### Delete Task +Deletes nth task in list. The first task has index 0. +##### Command +`delete ` +##### Example +`delete 0` + +
+ +#### Mark Task +Marks nth task in list. The first task has index 0. +##### Command +`mark ` +##### Example +`mark 0` +##### Chaterpillar Response +"I've marked this task as done." + +
+ +#### Unmark Task +Unmarks nth task in list. The first task has index 0. +##### Command +`unmark ` +##### Example +`unmark 0` +#### Chaterpillar Response +"I've marked this task as not done." + +
+ +### Contact List +Chaterpillar includes a simple contact list feature which stores contact names and their telegram handles. This contact list is stored on your hard drive and will be loaded each time you open the app. + +#### Add Contact +Adds a contact (name and telegram handle) to your contact list. +##### Command +`addContact t/` +##### Example +`addContact Gernene Tan t/gernene` +##### Chaterpillar Response +"I've added the contact: Gernene Tan: gernene" + +
+ +#### List Contacts +Shows an ordered list (starting from 0) of all your contacts. +##### Command +`listContacts` +##### Chaterpillar Response +"Your contacts: ..." + +
+ +#### Delete Contact +Removes a contact from your contact list. +##### Command +`deleteContact ` +##### Example +`deleteContact 0` +##### Chaterpillar Response +"I've deleted this contact." + +
+ +## Commands + +- `deadline /by ` Adds new deadline to your list. +- `event /at ` Adds new event to your list. +- `todo ` Adds new todo item to your list. +- `delete ` Deletes task at index n. +- `mark ` Marks task with index n as done. +- `unmark ` Marks task at index n as not done. +- `list` Displays all items in task list. +- `find ` Displays all task list items which contain specified string. +- `addContact t/` Adds new contact. +- `listContacts` Lists all contacts. +- `deleteContact ` Deletes contact at index n. +- `bye` Closes app. diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..d98d802b43 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..7454180f2a 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..2e6e5897b5 --- /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-7.3.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..1b6c787337 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/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/master/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 + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${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 "$*" +} >&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 + 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" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + 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 + +# 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 \ + "$@" + +# 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..107acd32c4 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@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 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%"=="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/ip-all.jar b/ip-all.jar new file mode 100644 index 0000000000..8190558e32 Binary files /dev/null and b/ip-all.jar differ diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000000..762c1be80e --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/7.3.3/userguide/multi_project_builds.html + */ + +rootProject.name = 'ip' 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/data/duke.txt b/src/main/java/data/duke.txt new file mode 100644 index 0000000000..1b202c72e5 --- /dev/null +++ b/src/main/java/data/duke.txt @@ -0,0 +1,4 @@ +T,true,read book +D,false,return book,2022-06-06 +E,true,project meeting,2022-08-06 +T,false,join sports club \ No newline at end of file diff --git a/src/main/java/duke/Contact.java b/src/main/java/duke/Contact.java new file mode 100644 index 0000000000..03929617e8 --- /dev/null +++ b/src/main/java/duke/Contact.java @@ -0,0 +1,46 @@ +package duke; + +/** + * Represents a contact + */ +public class Contact { + + // Identity fields + private String name; + private String telegram; + + /** + * Constructs contact. + */ + public Contact(String name, String telegram) { + this.name = name; + this.telegram = telegram; + } + + public String getName() { + return name; + } + + public String getTelegram() { + return telegram; + } + + /** + * Returns string representation of contact. + * + * Returns string containing contact details. + */ + @Override + public String toString() { + return String.format("%s: %s", getName(), getTelegram()); + } + + /** + * Returns string representation of a contact. + * @return Contact as a string + */ + public String toDataString() { + return String.format("%s,%s", getName(), getTelegram()); + } + +} diff --git a/src/main/java/duke/ContactList.java b/src/main/java/duke/ContactList.java new file mode 100644 index 0000000000..e833fa72c2 --- /dev/null +++ b/src/main/java/duke/ContactList.java @@ -0,0 +1,71 @@ +package duke; + +import java.util.ArrayList; + +/** + * Contact list class + */ +public class ContactList { + + private ArrayList contacts; + + /** + * Constructs contact list. + `*/ + public ContactList(ArrayList contacts) { + this.contacts = contacts; + } + + /** + * Construct contact list. + */ + public ContactList() { + this(new ArrayList()); + } + + /** + * Adds contact to list. + * + * @param contact Contact to add. + */ + public void add(Contact contact) { + contacts.add(contact); + } + + /** + * Deletes contact with specified id. + * + * @param contactNumber Contact id. + */ + public void delete(int contactNumber) { + contacts.remove(contactNumber); + } + + /** + * Returns contact list. + * + * @return String representation of contact list. + */ + public String toString() { + final StringBuilder builder = new StringBuilder(); + for (Contact contact : contacts) { + builder.append(contact.toString()).append("\n"); + } + return builder.toString(); + } + + /** + * Returns string representation of list for storage. + * + * @return Data of all contacts. + */ + public String toDataString() { + String string = ""; + for (int i = 0; i < contacts.size(); i++) { + Contact contact = contacts.get(i); + string += String.format("%s\n", contact.toDataString()); + } + return string; + } + +} diff --git a/src/main/java/duke/DialogBox.java b/src/main/java/duke/DialogBox.java new file mode 100644 index 0000000000..c04c1eed2c --- /dev/null +++ b/src/main/java/duke/DialogBox.java @@ -0,0 +1,66 @@ +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.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.text.Text; + +/** + * 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 Text dialog; + @FXML + private ImageView displayPicture; + @FXML + private HBox chatBubble; + + private DialogBox(String text, Image img, String chatClass) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + chatBubble.getStyleClass().add(chatClass); + dialog.setText(text); + dialog.setWrappingWidth(220); + displayPicture.setImage(img); + this.setSpacing(10); + } + + /** + * 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, "user"); + } + + public static DialogBox getDukeDialog(String text, Image img) { + var db = new DialogBox(text, img, null); + 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..cdd656bf97 --- /dev/null +++ b/src/main/java/duke/Duke.java @@ -0,0 +1,55 @@ +package duke; + +import java.nio.file.Path; + +import duke.command.Command; + +public class Duke { + + private Storage storage; + private TaskList tasks; + private Ui ui; + private ContactList contacts; + + /** + * Initializes task list and reads tasks from storage. + * + * @param filePath File path to read task data from. + */ + public Duke(Path directory, Path filePath, Path contactsPath) { + ui = new Ui(); + storage = new Storage(directory, filePath, contactsPath); + + try { + tasks = new TaskList(storage.load()); + } catch (DukeException e) { + System.out.println(e.getMessage()); + tasks = new TaskList(); + } + + try { + contacts = new ContactList(storage.loadContacts()); + } catch (DukeException e) { + System.out.println(e.getMessage()); + contacts = new ContactList(); + } + } + + /** + * Gets Duke's response to user. + * + * @param input User input. + * @return Duke's response to user. + */ + public String getResponse(String input) { + String response; + try { + Command c = Parser.parse(input); + response = c.execute(tasks, ui, storage, contacts); + } catch (DukeException e) { + response = ui.showError(e.getMessage()); + } + return response; + } + +} diff --git a/src/main/java/duke/DukeException.java b/src/main/java/duke/DukeException.java new file mode 100644 index 0000000000..73c1a8d300 --- /dev/null +++ b/src/main/java/duke/DukeException.java @@ -0,0 +1,9 @@ +package duke; + +public class DukeException extends Exception { + + public DukeException(String message) { + super(message); + } + +} diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java new file mode 100644 index 0000000000..e4ef6b4628 --- /dev/null +++ b/src/main/java/duke/Launcher.java @@ -0,0 +1,12 @@ +package duke; + +import javafx.application.Application; + +/** + * A launcher class to workaround classpath issues. + */ +public class Launcher { + public static void main(String[] args) { + Application.launch(Main.class, args); + } +} diff --git a/src/main/java/duke/Main.java b/src/main/java/duke/Main.java new file mode 100644 index 0000000000..8fddda5e8c --- /dev/null +++ b/src/main/java/duke/Main.java @@ -0,0 +1,44 @@ +package duke; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +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 Duke duke; + + /** + * Connects Duke backend with FXML UI. + * + * @param stage FXML stage. + */ + @Override + public void start(Stage stage) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml")); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + scene.getStylesheets().add(Main.class.getResource("/css/style.css").toExternalForm()); + String home = System.getProperty("user.dir"); + Path directory = Paths.get(home, "duke"); + Path filePath = Paths.get(home, "duke", "data.txt"); + Path contactsPath = Paths.get(home, "duke", "contacts.txt"); + duke = new Duke(directory, filePath, contactsPath); + stage.setScene(scene); + fxmlLoader.getController().setDuke(duke); + 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..f762ccd145 --- /dev/null +++ b/src/main/java/duke/MainWindow.java @@ -0,0 +1,58 @@ +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; +/** + * 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 Image userImage = new Image(this.getClass().getResourceAsStream("/images/IceCream.gif")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/Wallie.gif")); + + + /** + * Initializes main window and shows welcome message. + */ + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + dialogContainer.getChildren().addAll( + DialogBox.getDukeDialog( + "Welcome to Chaterpillar, your todo list & contacts buddy! :D", dukeImage) + ); + } + + public void setDuke(Duke d) { + duke = d; + } + + /** + * Creates two dialog boxes, one echoing user input and the other containing Duke's reply. + */ + @FXML + private void handleUserInput() { + String input = userInput.getText(); + String response = duke.getResponse(input); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(input, userImage), + DialogBox.getDukeDialog(response, dukeImage) + ); + userInput.clear(); + } +} diff --git a/src/main/java/duke/Parser.java b/src/main/java/duke/Parser.java new file mode 100644 index 0000000000..76d6ba3edd --- /dev/null +++ b/src/main/java/duke/Parser.java @@ -0,0 +1,254 @@ +package duke; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +import duke.command.AddCommand; +import duke.command.AddContactCommand; +import duke.command.Command; +import duke.command.DeleteCommand; +import duke.command.DeleteContactCommand; +import duke.command.ExitCommand; +import duke.command.FindCommand; +import duke.command.ListCommand; +import duke.command.ListContactsCommand; +import duke.command.MarkCommand; +import duke.command.UnmarkCommand; +import duke.task.Deadline; +import duke.task.Event; +import duke.task.Task; +import duke.task.Todo; + +public class Parser { + + public Parser() { + + } + + /** + * Parses command line input. + * + * @param fullCommand String to parse. + * @return Command instance. + * @throws DukeException If command is not recognized. + */ + public static Command parse(String fullCommand) throws DukeException { + String[] fullCommandArray = fullCommand.split(" "); + assert fullCommandArray.length > 0; + String command = fullCommandArray[0]; + if (command.equals("deadline")) { + return parseAddDeadlineCommand(fullCommand); + } else if (command.equals("event")) { + return parseAddEventCommand(fullCommand); + } else if (command.equals("todo")) { + return parseAddTodoCommand(fullCommand); + } else if (command.equals("list")) { + return parseListCommand(); + } else if (command.equals("listContacts")) { + return parseListContactsCommand(); + } else if (command.equals("delete")) { + return parseDeleteCommand(fullCommand); + } else if (command.equals("mark")) { + return parseMarkCommand(fullCommand); + } else if (command.equals("unmark")) { + return parseUnmarkCommand(fullCommand); + } else if (command.equals("bye")) { + return parseExitCommand(); + } else if (command.equals("find")) { + return parseFindCommand(fullCommand); + } else if (command.equals("addContact")) { + return parseAddContactCommand(fullCommand); + } else if (command.equals("deleteContact")) { + return parseDeleteContactCommand(fullCommand); + } else { + throw new DukeException("I don't recognize that command."); + } + } + + /** + * Parses add deadline command. + * + * @param fullCommand String to parse. + * @return AddCommand instance. + */ + private static Command parseAddDeadlineCommand(String fullCommand) throws DukeException { + try { + String dataString = fullCommand.replaceFirst("deadline ", ""); + String[] data = dataString.split(" /by "); + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + LocalDateTime dateTime = LocalDateTime.parse(data[1], dateFormatter); + Task task = new Deadline(data[0], dateTime); + return new AddCommand(task); + } catch (IndexOutOfBoundsException e) { + throw new DukeException("This command is missing inputs."); + } catch (DateTimeParseException e) { + throw new DukeException("Please specify datetime in the format yyyy-MM-dd HH:mm"); + } + } + + /** + * Parses add event command. + * + * @param fullCommand String to parse. + * @return AddCommand instance. + */ + private static Command parseAddEventCommand(String fullCommand) throws DukeException { + try { + String dataString = fullCommand.replaceFirst("event ", ""); + String[] data = dataString.split(" /at "); + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + LocalDateTime dateTime = LocalDateTime.parse(data[1], dateTimeFormatter); + Task task = new Event(data[0], dateTime); + return new AddCommand(task); + } catch (IndexOutOfBoundsException e) { + throw new DukeException("This command is missing inputs."); + } catch (DateTimeParseException e) { + throw new DukeException("Please specify datetime in the format yyyy-MM-dd HH:mm"); + } + } + + /** + * Parses add todo command. + * + * @param fullCommand String to parse. + * @return AddCommand instance. + * @throws DukeException If command is not recognized. + */ + private static Command parseAddTodoCommand(String fullCommand) throws DukeException { + String dataString = fullCommand.replaceFirst("todo ", ""); + if (dataString.trim().equals("")) { + throw new DukeException("Todo needs a description"); + } + Task task = new Todo(dataString); + return new AddCommand(task); + } + + /** + * Parses list command. + * + * @param fullCommand String to parse. + * @return ListCommand instance. + */ + private static Command parseListCommand() { + return new ListCommand(); + } + + /** + * Parses list contacts command. + * + * @param fullCommand String to parse. + * @return ListContactsCommand instance. + */ + private static Command parseListContactsCommand() { + return new ListContactsCommand(); + } + + /** + * Parses delete command. + * + * @param fullCommand String to parse. + * @return DeleteCommand instance. + */ + private static Command parseDeleteCommand(String fullCommand) throws DukeException { + try { + String dataString = fullCommand.replaceFirst("delete ", ""); + int taskNumber = Integer.parseInt(dataString); + return new DeleteCommand(taskNumber); + } catch (NumberFormatException e) { + throw new DukeException("Please specify a valid task id. This id should be an integer."); + } + } + + /** + * Parses mark command. + * + * @param fullCommand String to parse. + * @return MarkCommand instance. + */ + private static Command parseMarkCommand(String fullCommand) throws DukeException { + try { + String dataString = fullCommand.replaceFirst("mark ", ""); + int taskNumber = Integer.parseInt(dataString); + return new MarkCommand(taskNumber); + } catch (NumberFormatException e) { + throw new DukeException("Please specify a valid task id. This id should be an integer."); + } + } + + /** + * Parses unmark command. + * + * @param fullCommand String to parse. + * @return UnmarkCommand instance. + */ + private static Command parseUnmarkCommand(String fullCommand) throws DukeException { + try { + String dataString = fullCommand.replaceFirst("unmark ", ""); + int taskNumber = Integer.parseInt(dataString); + return new UnmarkCommand(taskNumber); + } catch (NumberFormatException e) { + throw new DukeException("Please specify a valid task id. This id should be an integer."); + } + } + + /** + * Parses find command. + * + * @param fullCommand String to parse. + * @return FindCommand instance. + */ + private static Command parseFindCommand(String fullCommand) throws DukeException { + String dataString = fullCommand.replaceFirst("find ", ""); + if (dataString.trim().equals("")) { + throw new DukeException("Please specify a string to find."); + } + return new FindCommand(dataString); + } + + /** + * Parses exit command. + * + * @param fullCommand String to parse. + * @return ExitCommand instance. + */ + private static Command parseExitCommand() { + return new ExitCommand(); + } + + /** + * Parses delete contact command. + * + * @param fullCommand String to parse. + * @return DeleteContactCommand instance. + */ + private static Command parseDeleteContactCommand(String fullCommand) throws DukeException { + try { + String dataString = fullCommand.replaceFirst("deleteContact ", ""); + int contactNumber = Integer.parseInt(dataString); + return new DeleteContactCommand(contactNumber); + } catch (NumberFormatException e) { + throw new DukeException("Please specify a valid contact id. This id should be an integer."); + } + } + + /** + * Parses add contact command. + * + * @param fullCommand String to parse. + * @return AddContactCommand instance. + */ + private static Command parseAddContactCommand(String fullCommand) throws DukeException { + try { + String dataString = fullCommand.replaceFirst("addContact ", ""); + String[] dataArr = dataString.split(" t/"); + String name = dataArr[0]; + String telegram = dataArr[1]; + Contact contact = new Contact(name, telegram); + return new AddContactCommand(contact); + } catch (IndexOutOfBoundsException e) { + throw new DukeException("This command might be missing inputs."); + } + } + +} diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/Storage.java new file mode 100644 index 0000000000..ac292ab3e3 --- /dev/null +++ b/src/main/java/duke/Storage.java @@ -0,0 +1,176 @@ +package duke; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Scanner; + +import duke.task.Deadline; +import duke.task.Event; +import duke.task.Task; +import duke.task.Todo; + +/** + * Handles hard disk storage for task list. + */ + +public class Storage { + + private Path directory; + private Path filePath; + private Path contactsPath; + + /** + * Constructs storage object for task list. + * + * @param directory Directory file is in. + * @param filePath Full path to file including file name. + * @param contactsPath Full path to contacts list file including file name. + */ + public Storage(Path directory, Path filePath, Path contactsPath) { + this.directory = directory; + this.filePath = filePath; + this.contactsPath = contactsPath; + } + + /** + * Loads list of tasks from file. + * + * @return List of tasks. + * @throws DukeException If file not found or file data corrupted. + */ + public ArrayList load() throws DukeException { + ArrayList tasks = new ArrayList(); + try { + File dir = directory.toFile(); + if (!dir.exists()) { + dir.mkdirs(); + } + File file = filePath.toFile(); + file.createNewFile(); + Scanner fileScanner = new Scanner(file); + while (fileScanner.hasNextLine()) { + String taskString = fileScanner.nextLine(); + Task task = constructTask(taskString); + tasks.add(task); + } + fileScanner.close(); + } catch (IOException e) { + System.out.println("Something went wrong"); + } + return tasks; + } + + /** + * Constructs Task given data string. + * + * @return Task object. + * @throws DukeException If string is not in correct format. + */ + public Task constructTask(String taskString) throws DukeException { + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + String[] data = taskString.split(","); + String type = data[0]; + Boolean status = Boolean.parseBoolean(data[1]); + String text = data[2]; + + if (type.equals("T")) { + return new Todo(text, status); + } else if (type.equals("D")) { + LocalDateTime date = LocalDateTime.parse(data[3], dateFormatter); + return new Deadline(text, status, date); + } else if (type.equals("E")) { + LocalDateTime date = LocalDateTime.parse(data[3], dateFormatter); + return new Event(text, status, date); + } else { + throw new DukeException("Cannot parse task list file data."); + } + } + + /** + * Writes task list to file. + * + * @throws DukeException If couldn't write to file. + */ + public void save(TaskList tasks) throws DukeException { + try { + File file = filePath.toFile(); + file.createNewFile(); + FileWriter myWriter = new FileWriter(file); + myWriter.write(tasks.toDataString()); + myWriter.close(); + } catch (IOException e) { + throw new DukeException("Couldn't write to file"); + } + } + + /** + * Writes task list to file. + * + * @throws DukeException If couldn't write to file. + */ + public void save(ContactList contacts) throws DukeException { + try { + File file = contactsPath.toFile(); + file.createNewFile(); + FileWriter myWriter = new FileWriter(file); + myWriter.write(contacts.toDataString()); + myWriter.close(); + } catch (IOException e) { + throw new DukeException("Couldn't write to file"); + } + } + + /** + * Loads list of contacts from file. + * + * @return List of contacts. + * @throws DukeException If file not found or file data corrupted. + */ + public ArrayList loadContacts() throws DukeException { + ArrayList contacts = new ArrayList(); + try { + File dir = directory.toFile(); + if (!dir.exists()) { + dir.mkdirs(); + } + File file = contactsPath.toFile(); + file.createNewFile(); + Scanner fileScanner = new Scanner(file); + while (fileScanner.hasNextLine()) { + String contactString = fileScanner.nextLine(); + Contact contact = constructContact(contactString); + contacts.add(contact); + } + fileScanner.close(); + } catch (IOException e) { + System.out.println("Something went wrong"); + } + return contacts; + } + + + + /** + * Constructs Contact given data string. + * + * @return Contact object. + * @throws DukeException If string is not in correct format. + */ + public Contact constructContact(String contactString) throws DukeException { + String[] data = contactString.split(","); + String name = data[0]; + String telegram = data[1]; + + if (data.length != 2) { + throw new DukeException("Invalid contact format."); + } else { + return new Contact(name, telegram); + } + } + +} diff --git a/src/main/java/duke/TaskList.java b/src/main/java/duke/TaskList.java new file mode 100644 index 0000000000..aba3b8e60a --- /dev/null +++ b/src/main/java/duke/TaskList.java @@ -0,0 +1,107 @@ +package duke; + +import java.util.ArrayList; + +import duke.task.Task; + +public class TaskList { + private ArrayList tasks; + + public TaskList(ArrayList tasks) { + this.tasks = tasks; + } + + public TaskList() { + this(new ArrayList()); + } + + /** + * Gets task at index n. + * + * @param n Specified index to fetch. + */ + public Task get(int n) { + return tasks.get(n); + } + + /** + * Adds specified task to list. + * + * @param task Specified task to add. + */ + public void addTask(Task task) { + tasks.add(task); + } + + /** + * Deletes task at specified index. + * + * @param n List index of the task to delete. + */ + public void deleteTask(int n) { + tasks.remove(n); + } + + /** + * Marks task at specified index as done. + * + * @param n List index of the task to mark. + */ + public void markTask(int n) { + tasks.get(n).mark(); + } + + /** + * Marks task at specified index as not done. + * + * @param n List index of the task to unmark. + */ + public void unmarkTask(int n) { + tasks.get(n).unmark(); + } + + /** + * Finds tasks which match a specified string. + * + * @param str String to match. + * @return Matching tasks. + */ + public TaskList find(String str) { + TaskList matchingTasks = new TaskList(); + for (Task task : tasks) { + if (task.contains(str)) { + matchingTasks.addTask(task); + } + } + return matchingTasks; + } + + /** + * Returns string representation of list. + * + * @return List of all tasks. + */ + public String toString() { + String string = ""; + for (int i = 0; i < tasks.size(); i++) { + Task task = tasks.get(i); + string += String.format("%d. %s\n", i, task.toString()); + } + return string; + } + + /** + * Returns string representation of list for storage. + * + * @return Data of all tasks. + */ + public String toDataString() { + String string = ""; + for (int i = 0; i < tasks.size(); i++) { + Task task = tasks.get(i); + string += String.format("%s\n", task.toDataString()); + } + return string; + } + +} diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/Ui.java new file mode 100644 index 0000000000..9b3d81c91d --- /dev/null +++ b/src/main/java/duke/Ui.java @@ -0,0 +1,152 @@ +package duke; + +import java.util.Scanner; + +import duke.task.Task; + +public class Ui { + + private Scanner scanner; + + public Ui() { + scanner = new Scanner(System.in); + } + + /** + * Reads line from command line input. + * + * @return Line from command line. + */ + public String readCommand() { + return scanner.nextLine(); + } + + /** + * Displays welcome message when user starts app. + * + * @return String to display. + */ + public String showWelcome() { + return "Hello from Duke!"; + } + + /** + * Shows display when a task is added. + * + * @return String to display. + */ + public String showTaskAdded(Task task) { + return String.format("I've added the task: %s", task.toString()); + } + + /** + * Shows display when a task is deleted. + * + * @return String to display. + */ + public String showTaskDeleted() { + return "I've deleted this task."; + } + + /** + * Shows display when a task is marked. + * + * @return String to display. + */ + public String showTaskMarked() { + return "I've marked this task as done."; + } + + /** + * Shows display when a task is unmarked. + * + * @return String to display. + */ + public String showTaskUnmarked() { + return "I've marked this task as not done."; + } + + /** + * Shows list of all tasks in specified list + * + * @param tasks List of all tasks. + * @return String to display. + */ + public String showTasks(TaskList tasks) { + return String.format("Your todo list:\n\n%s", tasks.toString()); + } + + /** + * Shows list of all contacts in specified list + * + * @param contacts List of all contacts. + * @return String to display. + */ + public String showContacts(ContactList contacts) { + return String.format("Your telegram contacts:\n\n%s", contacts.toString()); + } + + /** + * Shows display when a contact is added. + * + * @return String to display. + */ + public String showContactAdded(Contact contact) { + return String.format("I've added the contact: %s", contact.toString()); + } + + /** + * Shows display when a contact is deleted. + * + * @return String to display. + */ + public String showContactDeleted() { + return String.format("I've deleted this contact."); + } + + /** + * Shows display when user exits app. + * + * @return String to display. + */ + public String showMessage(String str) { + return str; + } + + /** + * Shows display when user exits app. + * + * @return String to display. + */ + public String showExit() { + return "Goodbye!"; + } + + /** + * Displays specified error message. + * + * @return String to display. + */ + public String showError(String message) { + return message; + } + + /** + * Shows divider. + * + * @return String to display. + */ + public String showLine() { + return "_____"; + } + + /** + * Shows loading error. + * + * @return String to display. + */ + public String showLoadingError() { + return "Loading error"; + } + +} diff --git a/src/main/java/duke/command/AddCommand.java b/src/main/java/duke/command/AddCommand.java new file mode 100644 index 0000000000..11361eeec5 --- /dev/null +++ b/src/main/java/duke/command/AddCommand.java @@ -0,0 +1,45 @@ +package duke.command; + +import duke.ContactList; +import duke.DukeException; +import duke.Storage; +import duke.TaskList; +import duke.Ui; +import duke.task.Task; + +/** + * Adds a task to the task list. + */ +public class AddCommand extends Command { + + private Task task; + + /** + * Constructs AddCommand. + * + * @param task Task to add to the list. + */ + public AddCommand(Task task) { + super(); + this.task = task; + } + + /** + * Adds the specified task to the list. + * + * @param tasks List to add task to. + * @param ui Interface to display results to. + * @param storage File storage of tasks. + * @param contacts List of contacts. + */ + public String execute(TaskList tasks, Ui ui, Storage storage, ContactList contacts) { + try { + tasks.addTask(task); + storage.save(tasks); + return ui.showTaskAdded(task); + } catch (DukeException e) { + return ui.showError(e.getMessage()); + } + } + +} diff --git a/src/main/java/duke/command/AddContactCommand.java b/src/main/java/duke/command/AddContactCommand.java new file mode 100644 index 0000000000..22118b95ae --- /dev/null +++ b/src/main/java/duke/command/AddContactCommand.java @@ -0,0 +1,45 @@ +package duke.command; + +import duke.Contact; +import duke.ContactList; +import duke.DukeException; +import duke.Storage; +import duke.TaskList; +import duke.Ui; + +/** + * Adds a contact to the contact list. + */ +public class AddContactCommand extends Command { + + private Contact contact; + + /** + * Constructs AddCommand. + * + * @param contact Contact to add to the list. + */ + public AddContactCommand(Contact contact) { + super(); + this.contact = contact; + } + + /** + * Adds the specified task to the list. + * + * @param tasks List to add task to. + * @param ui Interface to display results to. + * @param storage File storage of tasks. + * @param contacts List of contacts. + */ + public String execute(TaskList tasks, Ui ui, Storage storage, ContactList contacts) { + try { + contacts.add(contact); + storage.save(contacts); + return ui.showContactAdded(contact); + } catch (DukeException e) { + return ui.showError(e.getMessage()); + } + } + +} diff --git a/src/main/java/duke/command/Command.java b/src/main/java/duke/command/Command.java new file mode 100644 index 0000000000..eca296ba33 --- /dev/null +++ b/src/main/java/duke/command/Command.java @@ -0,0 +1,39 @@ +package duke.command; + +import duke.ContactList; +import duke.Storage; +import duke.TaskList; +import duke.Ui; + +/** + * Executable command. + */ +public abstract class Command { + + /** + * Constructs Command. + */ + public Command() { + + } + + /** + * Checks if this command is the exit command. + * + * @return False by default. This is overriden in the ExitCommand subclass. + */ + public boolean isExit() { + return false; + } + + /** + * Executes this command. + * + * @param tasks List to add task to. + * @param ui Interface to display results to. + * @param storage File storage of tasks. + * @param contacts Lists of contacts. + */ + public abstract String execute(TaskList tasks, Ui ui, Storage storage, ContactList contacts); + +} diff --git a/src/main/java/duke/command/DeleteCommand.java b/src/main/java/duke/command/DeleteCommand.java new file mode 100644 index 0000000000..5f307d8d26 --- /dev/null +++ b/src/main/java/duke/command/DeleteCommand.java @@ -0,0 +1,42 @@ +package duke.command; + +import duke.ContactList; +import duke.DukeException; +import duke.Storage; +import duke.TaskList; +import duke.Ui; + +/** + * Delete command. Deletes a task from the list. + */ +public class DeleteCommand extends Command { + + private int taskNumber; + + /** + * Constructs delete command. + */ + public DeleteCommand(int taskNumber) { + super(); + this.taskNumber = taskNumber; + } + + /** + * Deletes the task with the specified id. + * + * @param tasks List to add task to. + * @param ui Interface to display results to. + * @param storage File storage of tasks. + * @param contacts List of contacts. + */ + public String execute(TaskList tasks, Ui ui, Storage storage, ContactList contacts) { + try { + tasks.deleteTask(taskNumber); + storage.save(tasks); + } catch (DukeException e) { + ui.showError(e.getMessage()); + } + return ui.showTaskDeleted(); + } + +} diff --git a/src/main/java/duke/command/DeleteContactCommand.java b/src/main/java/duke/command/DeleteContactCommand.java new file mode 100644 index 0000000000..a65d491648 --- /dev/null +++ b/src/main/java/duke/command/DeleteContactCommand.java @@ -0,0 +1,42 @@ +package duke.command; + +import duke.ContactList; +import duke.DukeException; +import duke.Storage; +import duke.TaskList; +import duke.Ui; + +/** + * Delete contact command. Deletes a contact from the contact list. + */ +public class DeleteContactCommand extends Command { + + private int contactNumber; + + /** + * Constructs delete command. + */ + public DeleteContactCommand(int contactNumber) { + super(); + this.contactNumber = contactNumber; + } + + /** + * Deletes the contact with the specified id. + * + * @param tasks List to add task to. + * @param ui Interface to display results to. + * @param storage File storage of tasks. + * @param contacts List of all contacts. + */ + public String execute(TaskList tasks, Ui ui, Storage storage, ContactList contacts) { + try { + contacts.delete(contactNumber); + storage.save(contacts); + return ui.showContactDeleted(); + } catch (DukeException e) { + return ui.showError(e.getMessage()); + } + } + +} diff --git a/src/main/java/duke/command/ExitCommand.java b/src/main/java/duke/command/ExitCommand.java new file mode 100644 index 0000000000..b7092d3e55 --- /dev/null +++ b/src/main/java/duke/command/ExitCommand.java @@ -0,0 +1,42 @@ +package duke.command; + +import duke.ContactList; +import duke.Storage; +import duke.TaskList; +import duke.Ui; + +/** + * Exit command. Exits Duke. + */ +public class ExitCommand extends Command { + + /** + * Constructs exit command. + */ + public ExitCommand() { + super(); + } + + /** + * Exits Duke. + * + * @param tasks List to add task to. + * @param ui Interface to display results to. + * @param storage File storage of tasks. + * @param contacts List of contacts. + */ + public String execute(TaskList tasks, Ui ui, Storage storage, ContactList contacts) { + System.exit(0); + return ui.showExit(); + } + + /** + * Checks if this is the exit command. + * + * @return True. + */ + public boolean isExit() { + return true; + } + +} diff --git a/src/main/java/duke/command/FindCommand.java b/src/main/java/duke/command/FindCommand.java new file mode 100644 index 0000000000..d602d65f39 --- /dev/null +++ b/src/main/java/duke/command/FindCommand.java @@ -0,0 +1,38 @@ +package duke.command; + +import duke.ContactList; +import duke.Storage; +import duke.TaskList; +import duke.Ui; + +/** + * Find command. Finds all tasks which contain a specified string. + */ +public class FindCommand extends Command { + + private String str; + + /** + * Constructs find command. + * + * @param str String to match. + */ + public FindCommand(String str) { + super(); + this.str = str; + } + + /** + * Finds all tasks which contain a specified string. + * + * @param tasks List to add task to. + * @param ui Interface to display results to. + * @param storage File storage of tasks. + * @param contacts List of contacts. + */ + public String execute(TaskList tasks, Ui ui, Storage storage, ContactList contacts) { + TaskList matchingTasks = tasks.find(str); + return ui.showTasks(matchingTasks); + } + +} diff --git a/src/main/java/duke/command/ListCommand.java b/src/main/java/duke/command/ListCommand.java new file mode 100644 index 0000000000..257b979f96 --- /dev/null +++ b/src/main/java/duke/command/ListCommand.java @@ -0,0 +1,32 @@ +package duke.command; + +import duke.ContactList; +import duke.Storage; +import duke.TaskList; +import duke.Ui; + +/** + * List command. Shows task list. + */ +public class ListCommand extends Command { + + /** + * Constructs delete command. + */ + public ListCommand() { + super(); + } + + /** + * Shows list of all tasks. + * + * @param tasks List to add task to. + * @param ui Interface to display results to. + * @param storage File storage of tasks. + * @param contacts List of contacts. + */ + public String execute(TaskList tasks, Ui ui, Storage storage, ContactList contacts) { + return ui.showTasks(tasks); + } + +} diff --git a/src/main/java/duke/command/ListContactsCommand.java b/src/main/java/duke/command/ListContactsCommand.java new file mode 100644 index 0000000000..6f510008ee --- /dev/null +++ b/src/main/java/duke/command/ListContactsCommand.java @@ -0,0 +1,32 @@ +package duke.command; + +import duke.ContactList; +import duke.Storage; +import duke.TaskList; +import duke.Ui; + +/** + * List command. Shows task list. + */ +public class ListContactsCommand extends Command { + + /** + * Constructs delete command. + */ + public ListContactsCommand() { + super(); + } + + /** + * Shows list of all tasks. + * + * @param tasks List to add task to. + * @param ui Interface to display results to. + * @param storage File storage of tasks. + * @param contacts List of contacts. + */ + public String execute(TaskList tasks, Ui ui, Storage storage, ContactList contacts) { + return ui.showContacts(contacts); + } + +} diff --git a/src/main/java/duke/command/MarkCommand.java b/src/main/java/duke/command/MarkCommand.java new file mode 100644 index 0000000000..8fcfaabbd6 --- /dev/null +++ b/src/main/java/duke/command/MarkCommand.java @@ -0,0 +1,44 @@ +package duke.command; + +import duke.ContactList; +import duke.DukeException; +import duke.Storage; +import duke.TaskList; +import duke.Ui; + +/** + * Mark command. Marks specified task as done. + */ +public class MarkCommand extends Command { + + private int taskNumber; + + /** + * Constructs MarkCommand. + * + * @param taskNumber Id of task to delete. + */ + public MarkCommand(int taskNumber) { + super(); + this.taskNumber = taskNumber; + } + + /** + * Marks task with specified id as done. + * + * @param tasks List to add task to. + * @param ui Interface to display results to. + * @param storage File storage of tasks. + * @param contacts List of contacts. + */ + public String execute(TaskList tasks, Ui ui, Storage storage, ContactList contacts) { + try { + tasks.markTask(taskNumber); + storage.save(tasks); + return ui.showTaskMarked(); + } catch (DukeException e) { + return ui.showError(e.getMessage()); + } + } + +} diff --git a/src/main/java/duke/command/UnmarkCommand.java b/src/main/java/duke/command/UnmarkCommand.java new file mode 100644 index 0000000000..9f0e54f2ca --- /dev/null +++ b/src/main/java/duke/command/UnmarkCommand.java @@ -0,0 +1,44 @@ +package duke.command; + +import duke.ContactList; +import duke.DukeException; +import duke.Storage; +import duke.TaskList; +import duke.Ui; + +/** + * UnmarkCommand. Marks a task as not done. + */ +public class UnmarkCommand extends Command { + + private int taskNumber; + + /** + * Constructs UnmarkTask. + * + * @param taskNumber Index of task to unmark. + */ + public UnmarkCommand(int taskNumber) { + super(); + this.taskNumber = taskNumber; + } + + /** + * Marks task with specified id as not done. + * + * @param tasks List to add task to. + * @param ui Interface to display results to. + * @param storage File storage of tasks. + * @param contacts List of contacts. + */ + public String execute(TaskList tasks, Ui ui, Storage storage, ContactList contacts) { + try { + tasks.unmarkTask(taskNumber); + storage.save(tasks); + return ui.showTaskUnmarked(); + } catch (DukeException e) { + return ui.showError(e.getMessage()); + } + } + +} diff --git a/src/main/java/duke/task/Deadline.java b/src/main/java/duke/task/Deadline.java new file mode 100644 index 0000000000..0acae90451 --- /dev/null +++ b/src/main/java/duke/task/Deadline.java @@ -0,0 +1,56 @@ +package duke.task; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Deadline class. + */ +public class Deadline extends Task { + + private LocalDateTime date; + + /** + * Constructs deadline. + * @param description Task description. + * @param isMarked Indicates whether task is marked/done. + * @param date Deadline date + */ + public Deadline(String description, boolean isMarked, LocalDateTime date) { + super(description, isMarked); + this.date = date; + } + + /** + * Constructs deadline. + * @param description Task description. + * @param date Deadline date + */ + public Deadline(String description, LocalDateTime date) { + this(description, false, date); + } + + /** + * Returns string representation of a deadline. + * @return Deadline as a string + */ + public String toString() { + String status = super.isMarked() ? "X" : " "; + return String.format("[D][%s] %s (by: %s)", + status, + super.getDescription(), + date.format(DateTimeFormatter.ofPattern("MMM d yyyy HH:mm"))); + } + + /** + * Returns string representation of a deadline. + * @return Deadline as a string + */ + public String toDataString() { + return String.format("D,%s,%s,%s", + isMarked(), + super.getDescription(), + date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))); + } + +} diff --git a/src/main/java/duke/task/Event.java b/src/main/java/duke/task/Event.java new file mode 100644 index 0000000000..2a63ff7a1d --- /dev/null +++ b/src/main/java/duke/task/Event.java @@ -0,0 +1,56 @@ +package duke.task; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Event class. + */ +public class Event extends Task { + + private LocalDateTime date; + + /** + * Constructs event. + * @param description Task description. + * @param isMarked Indicates whether task is marked/done. + * @param date Event date + */ + public Event(String description, boolean status, LocalDateTime date) { + super(description, status); + this.date = date; + } + + /** + * Constructs event. + * @param description Task description. + * @param date Event date + */ + public Event(String description, LocalDateTime date) { + this(description, false, date); + } + + /** + * Returns string representation of an event. + * @return Event as a string + */ + public String toString() { + String status = super.isMarked() ? "X" : " "; + return String.format("[E][%s] %s (at: %s)", + status, + super.getDescription(), + date.format(DateTimeFormatter.ofPattern("MMM d yyyy HH:mm"))); + } + + /** + * Returns string representation of a event. + * @return Event as a string + */ + public String toDataString() { + return String.format("E,%s,%s,%s", + isMarked(), + super.getDescription(), + date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))); + } + +} diff --git a/src/main/java/duke/task/Task.java b/src/main/java/duke/task/Task.java new file mode 100644 index 0000000000..4cd5ed8005 --- /dev/null +++ b/src/main/java/duke/task/Task.java @@ -0,0 +1,84 @@ +package duke.task; + +/** + * Task class. Is extended by Deadline, Event, and Todo. + */ +public abstract class Task { + + private String description; + private boolean isMarked; + + /** + * Constructs task. + * @param description Task description. + * @param isMarked Indicates whether task is marked/done. + */ + public Task(String description, boolean isMarked) { + this.description = description; + this.isMarked = isMarked; + } + + /** + * Constructs task. + * @param description Task description. + */ + public Task(String description) { + this(description, false); + } + + /** + * Marks this task as done. + */ + public void mark() { + isMarked = true; + } + + /** + * Marks this task as not done. + */ + public void unmark() { + isMarked = false; + } + + /** + * Marks this task as not done. + * + * @return Task description. + */ + public String getDescription() { + return description; + } + + /** + * Marks this task as not done. + * + * @return Task description. + */ + public boolean isMarked() { + return isMarked; + } + + /** + * Checks if a string exists in this task's description. + * + * @return True if string is found and false otherwise. + */ + public boolean contains(String str) { + return description.matches(".*?\\b" + str + "\\b.*?"); + } + + /** + * Returns string representation of this class. + * + * @return String representation of this task. + */ + public abstract String toString(); + + /** + * Returns string representation of this class as stored in harddisk. + * + * @return String representation of this task. + */ + public abstract String toDataString(); + +} diff --git a/src/main/java/duke/task/Todo.java b/src/main/java/duke/task/Todo.java new file mode 100644 index 0000000000..3ab9ce2154 --- /dev/null +++ b/src/main/java/duke/task/Todo.java @@ -0,0 +1,44 @@ +package duke.task; + +/** + * Todo class. + */ +public class Todo extends Task { + + /** + * Constructs todo item. + * @param description Task description. + * @param isMarked Indicates whether task is marked/done. + */ + public Todo(String description, boolean isMarked) { + super(description, isMarked); + } + + /** + * Constructs todo item. + * @param description Task description. + */ + public Todo(String description) { + this(description, false); + } + + /** + * Returns string representation of a todo item. + * @return Todo as a string + */ + public String toString() { + String status = super.isMarked() ? "X" : " "; + return String.format("[T][%s] %s", status, super.getDescription()); + } + + /** + * Returns string representation of a todo. + * @return Todo as a string + */ + public String toDataString() { + return String.format("T,%s,%s", + isMarked(), + super.getDescription()); + } + +} diff --git a/text-ui-test/EXPECTED.TXT b/src/main/java/text-ui-test/EXPECTED.TXT similarity index 100% rename from text-ui-test/EXPECTED.TXT rename to src/main/java/text-ui-test/EXPECTED.TXT diff --git a/text-ui-test/input.txt b/src/main/java/text-ui-test/input.txt similarity index 100% rename from text-ui-test/input.txt rename to src/main/java/text-ui-test/input.txt diff --git a/text-ui-test/runtest.bat b/src/main/java/text-ui-test/runtest.bat similarity index 100% rename from text-ui-test/runtest.bat rename to src/main/java/text-ui-test/runtest.bat diff --git a/text-ui-test/runtest.sh b/src/main/java/text-ui-test/runtest.sh similarity index 100% rename from text-ui-test/runtest.sh rename to src/main/java/text-ui-test/runtest.sh diff --git a/src/main/resources/css/style.css b/src/main/resources/css/style.css new file mode 100644 index 0000000000..ed3bf6c5c9 --- /dev/null +++ b/src/main/resources/css/style.css @@ -0,0 +1,28 @@ +.root{ + -fx-font-size: 12pt; + -fx-font-family: sans-serif; + -fx-background: #e6eeff; +} + +.chat-bubble { + -fx-background-color: #fafbff; + -fx-background-radius: 10px; + -fx-padding: 1em; +} + +.chat-bubble.user { + -fx-background-color: #cfdbf5; +} + +.chat-bubble-fill { + -fx-fill: #2c3957; +} + +.send-button { + -fx-background-color: #fbe0ff; + -fx-text-fill: rgb(77, 0, 60); +} + +.send-button:hover { + -fx-background-color: #f3cff8; +} \ No newline at end of file diff --git a/src/main/resources/images/DaDuke.jpg b/src/main/resources/images/DaDuke.jpg new file mode 100644 index 0000000000..751e3ea821 Binary files /dev/null and b/src/main/resources/images/DaDuke.jpg differ diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png new file mode 100644 index 0000000000..3c82f45461 Binary files /dev/null and b/src/main/resources/images/DaUser.png differ diff --git a/src/main/resources/images/IceCream.gif b/src/main/resources/images/IceCream.gif new file mode 100644 index 0000000000..b8497c773c Binary files /dev/null and b/src/main/resources/images/IceCream.gif differ diff --git a/src/main/resources/images/Wallie.gif b/src/main/resources/images/Wallie.gif new file mode 100644 index 0000000000..4560794987 Binary files /dev/null and b/src/main/resources/images/Wallie.gif differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..b61a313316 --- /dev/null +++ b/src/main/resources/view/DialogBox.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..cdaf00d38a --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + +