diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..c474172e21 --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +duke.gui.Main_temp-Class: duke.Duke + diff --git a/README.md b/README.md index 8715d4d915..0f2208ab64 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Duke project template +# duke.Duke project template This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. @@ -13,7 +13,7 @@ Prerequisites: JDK 11, update Intellij to the most recent version. 1. If there are any further prompts, accept the defaults. 1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
In the same dialog, set the **Project language level** field to the `SDK default` option. -3. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: +3. After that, locate the `src/main/java/duke.Duke.java` file, right-click it, and choose `Run duke.Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: ``` Hello from ____ _ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..89faaca1c3 --- /dev/null +++ b/build.gradle @@ -0,0 +1,61 @@ +plugins { + id 'java' + id 'application' + id 'com.github.johnrengelman.shadow' version '7.1.2' + +} + + +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' + + String javaFxVersion = '17.0.7' + + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + +application { + //mainClass.set("seedu.duke.Duke") + mainClass.set("duke.gui.Launcher") +} + +shadowJar { + archiveBaseName = "duke" + archiveClassifier = null + archiveFileName = 'duke.jar' + dependsOn("distZip", "distTar") +} + +run{ + standardInput = System.in +} diff --git a/data/list.txt b/data/list.txt new file mode 100644 index 0000000000..23c091c9ed --- /dev/null +++ b/data/list.txt @@ -0,0 +1,5 @@ +E | 1 | new movie release | 2023-09-12 | 2023-09-15 +T | 1 | eat +T | 1 | buy milk +D | 0 | quiz | 2023-10-02 +D | 0 | supermarket discount! | 2023-10-10 diff --git a/docs/README.md b/docs/README.md index 8077118ebe..ae11998904 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,190 @@ -# User Guide +# Duke User Guide -## Features -### Feature-ABC +This chatbot's name is Duke. You can tell him what ever +tasks you plan to do, and he will help you remember them! -Description of the feature. +In addition, it **_automatically saves your list on your computer even +if you accidentally close it_**. So, your tasks are "safe" and you can +get back to it anytime. -### Feature-XYZ +## Command Summary -Description of the feature. +add todo task: `todo [task name]` -## Usage +add deadline task: `deadline [task name] /by [yyyy-mm-dd]` -### `Keyword` - Describe action +add event task: `event [task name] /from [yyyy-mm-dd] /to [yyyy-mm-dd]` -Describe the action and its outcome. +show the whole list: `list`. -Example of usage: +mark task as done: `mark [index]` -`keyword (optional arguments)` +mark task as undone: `unmark [index]` + +delete task: `delete [index]` + +find task: `find [substring of the task name you want to find]` + +show statistics: `stats` + +sort the list: `sort by start date` or `sort by end date` + +say goodbye: `bye` + +## Command details + +### "todo": + +Format: `todo [task name]` + +Example use: `todo eat` + +Use this command to add a todo task which has no start date nor end +date. + +Expected outcome: + + Got it. I've added this task: + [T][ ] eat + Now you have 4 tasks in the list. + + +### "deadline": + +Format: `deadline [task name] /by [yyyy-mm-dd]` + +Example use: `deadline read book /by 2022-01-01` + +Use this command to add a deadline task which has only end +date, but does not have a start date. + +Expected outcome: + + Got it. I've added this task: + [D][ ] read book (by: 12-29-2023) + Now you have 4 tasks in the list. + +### "event": + +Format: `event [task name] /from [yyyy-mm-dd] /to [yyyy-mm-dd]` + +Example use: `event sleep /from 2022-01-01 /to 2022-01-02` + +Use this command to add an event task which has both start and end +date. + +Expected outcome: + + Got it. I've added this task: + [E][ ] sleep (from 12-10-2023 to: 12-11-2023) + Now you have 4 tasks in the list. + +### "list": + +Format: `list`. + +Example use: `list` + +Use this command to view all the tasks currently in your list. + +Expected outcome: + +**Duke will show the whole list** + +### "mark": + +Format: `mark [index]` + +Example use: `mark 1` + +Use this command to mark the task at given index as "done". + +Expected outcome: + + Following task is marked as done: + 1. [T][X] eat + +### "unmark": + +Format: `unmark [index]` + +Example use: `unmark 1` + +Use this command to mark the task at given index as "undone". + +Expected outcome: + + Following task is marked as undone: + 1. [T][ ] eat + +### "delete": + +Format: `delete [index]` + +Example use: `delete 1` + +Use this command to delete the task at given index. + +Expected outcome: + + Noted. I've removed this task: + [T][ ] read book + Now you have 4 tasks in the list. + +### "find": + +Format: `find [substring of the task name you want to find]` + +Example use: `find book` + +Use this command to search tasks which contain given substring. +In the example given above, Duke shall return "read book" and "return +book" if these two tasks are in your list. + +Expected outcome: + + Here are the matching tasks in your list: + 1. [T][ ] read book + +### "stats": + +Format: `stats` + +Example use: `stats` + +Use this command to see how many each type of task are in your list. + +Expected outcome: + + You have: + ~ 1 todo. + ~ 1 deadline. + ~ 2 event. + +### "sort": + +Format: `sort by start date` or `sort by end date` + +Example use: `sort by start date` or `sort by end date` + +Use this command to sort your list in ascending order of start date +or end date + +Expected outcome: + +**Duke will display the new sorted list.** + +### "bye": + +Format: `bye` + +Example use: `bye` + +Duke will also say goodbye to you :). Expected outcome: -Description of the outcome. + Bye. Hope to see you again soon! + -``` -expected output -``` diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..06894e04b3 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/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..1627c7e6cd --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: duke.gui.Launcher + 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/Duke.java b/src/main/java/duke/Duke.java new file mode 100644 index 0000000000..9ea99d736a --- /dev/null +++ b/src/main/java/duke/Duke.java @@ -0,0 +1,193 @@ +package duke; + +import duke.commands.Deadline; +import duke.commands.Delete; +import duke.commands.Event; +import duke.commands.Mark; +import duke.commands.Sort; +import duke.commands.Todo; +import duke.commands.Unmark; +import duke.helper.Storage; +import duke.helper.Ui; +import duke.helper.Parser; +import duke.helper.TaskList; + + +import java.util.Scanner; + + +public class Duke { + + + + private Ui ui; + private Storage storage; + private TaskList tasks; + private Parser parser; + + + public Duke(String fileName, String dirName) { + ui = new Ui("Blus"); + storage = new Storage(fileName, dirName); + try { + tasks = new TaskList(storage.loadList(), storage.getListPointer()); + } catch (Exception e) { + ui.speak(e.toString()); + tasks = new TaskList(); + } + parser = new Parser(); + } + + + + /** + * Handles interactions with the user in command line interface. + */ + public void run() { + + ui.speak(greet()); + + boolean isExiting = false; + + Scanner getUserInput = new Scanner(System.in); + + while (!(isExiting)) { + + String userInput = getUserInput.nextLine(); + + String message = getResponse(userInput); + if (message.equals(ui.exit())) { + isExiting = true; + } + + ui.speak(message); + + } + + getUserInput.close(); + + } + + + /** + * Handles interactions with the user in GUI. + * + * @param input User command. + * @return Blus's response to the user command. + */ + public String getResponse(String input) { + + + String message = ""; + parser.processUserCommand(input); + + //Invoke corresponding command handling using switch. Therefore, the switch statement is long + //because there are a number of available user command. + switch (parser.getCommand()) { + + case "bye": + //bye + message = bye(); + break; + + case "list": + //list + message = tasks.displayList(); + break; + + case "mark": + //mark 1 + Mark mark = new Mark(storage, parser, tasks); + message = mark.execute(); + break; + + case "unmark": + //unmark 1 + Unmark unmark = new Unmark(storage, parser, tasks); + message = unmark.execute(); + break; + + case "todo": + //todo read book + Todo todo = new Todo(storage, parser, tasks); + message = todo.execute(); + break; + + case "deadline": + //deadline read book /by 2022-01-01 + Deadline deadline = new Deadline(storage, parser, tasks); + message = deadline.execute(); + break; + + case "event": + //event read book /from 2022-01-01 /to 2022-01-02 + Event event = new Event(storage, parser, tasks); + message = event.execute(); + break; + + case "delete": + //delete 1 + Delete delete = new Delete(storage, parser, tasks); + message = delete.execute(); + break; + + case "find": + message = tasks.findTask(parser.getTaskName()); + break; + + case "stats": + message = tasks.showTaskStatistics(); + break; + + case "sort": + Sort sort = new Sort(storage, parser, tasks); + message = sort.execute(); + break; + + default: + message = "OOPS!!! I'm sorry, but I don't know what that means :-("; + } + + return message; + } + + + /** + * Returns greeting message. + * + * @return Greeting message. + */ + public String greet(){ + String logo = " ____ _ \n" + + " | _ \\| | \n" + + " | |_) | |_ _ ___ \n" + + " | _ <| | | | / __|\n" + + " | |_) | | |_| \\__ \\\n" + + " |____/|_|\\__,_|___/\n"; + + String message = "Hello from\n" + logo + "\n"; + message = message + ui.greet(); + + return message; + } + + /** + * Returns message for saying goodbye. + * + * @return Goodbye message. + */ + public String bye() { + return ui.exit(); + } + public static void main(String[] args) { + + new Duke("list.txt", "data").run(); + + + + } + + + + +} diff --git a/src/main/java/duke/commands/Command.java b/src/main/java/duke/commands/Command.java new file mode 100644 index 0000000000..6bd0330c5e --- /dev/null +++ b/src/main/java/duke/commands/Command.java @@ -0,0 +1,46 @@ +package duke.commands; + +import duke.helper.Parser; +import duke.helper.Storage; +import duke.helper.TaskList; + +import java.time.LocalDate; +import java.time.format.DateTimeParseException; + +public abstract class Command { + + public Storage storage; + public Parser parser; + public TaskList tasks; + + public Command(Storage storage, Parser parser, TaskList tasks) { + this.storage = storage; + this.parser = parser; + this.tasks = tasks; + } + + /** + * Handles commands. + * + * @return Blus's response to the user after processing the command. + */ + public abstract String execute(); + + /** + * Stores the current tasks list to file. + * + */ + public void store() { + storage.saveList(tasks.getUserList(), tasks.getUserListPointer()); + } + + public boolean isValidDateFormat(String date) { + try { + LocalDate d = LocalDate.parse(date); + + } catch (DateTimeParseException e) { + return false; + } + return true; + } +} diff --git a/src/main/java/duke/commands/Deadline.java b/src/main/java/duke/commands/Deadline.java new file mode 100644 index 0000000000..8436520602 --- /dev/null +++ b/src/main/java/duke/commands/Deadline.java @@ -0,0 +1,30 @@ +package duke.commands; + +import duke.helper.Parser; +import duke.helper.Storage; +import duke.helper.Task; +import duke.helper.TaskList; + +public class Deadline extends Command{ + + public Deadline(Storage storage, Parser parser, TaskList tasks) { + super(storage, parser, tasks); + } + + @Override + public String execute() { + String message; + if (parser.getTaskName().isEmpty()) { + return "Task name cannot be empty"; + } + if (isValidDateFormat(parser.getFirstEnteredTime())) { + message = tasks.addTask(new Task(parser.getTaskName(), + 2, "Null", parser.getFirstEnteredTime(), false)); + store(); + } else { + message = "Invalid date format. Please enter as /by yyyy-mm-dd."; + } + + return message; + } +} \ No newline at end of file diff --git a/src/main/java/duke/commands/Delete.java b/src/main/java/duke/commands/Delete.java new file mode 100644 index 0000000000..84199f57ad --- /dev/null +++ b/src/main/java/duke/commands/Delete.java @@ -0,0 +1,26 @@ +package duke.commands; + +import duke.helper.Parser; +import duke.helper.Storage; +import duke.helper.TaskList; + +public class Delete extends Command{ + + public Delete(Storage storage, Parser parser, TaskList tasks) { + super(storage, parser, tasks); + } + + @Override + public String execute() { + String message; + try { + int i = Integer.parseInt(parser.getTaskName()) - 1; + message = tasks.deleteTask(i); + store(); + } catch (NumberFormatException e) { + message = "need to provide an integer index of task."; + } + + return message; + } +} \ No newline at end of file diff --git a/src/main/java/duke/commands/Event.java b/src/main/java/duke/commands/Event.java new file mode 100644 index 0000000000..a0f76d6b82 --- /dev/null +++ b/src/main/java/duke/commands/Event.java @@ -0,0 +1,31 @@ +package duke.commands; + +import duke.helper.Parser; +import duke.helper.Storage; +import duke.helper.Task; +import duke.helper.TaskList; + +public class Event extends Command{ + + public Event(Storage storage, Parser parser, TaskList tasks) { + super(storage, parser, tasks); + } + + @Override + public String execute() { + String message; + if (parser.getTaskName().isEmpty()) { + return "Task name cannot be empty"; + } + if (isValidDateFormat(parser.getFirstEnteredTime()) && + isValidDateFormat(parser.getSecondEnteredTime())) { + message = tasks.addTask(new Task(parser.getTaskName(), + 3, parser.getFirstEnteredTime(), parser.getSecondEnteredTime(), false)); + store(); + } else { + message = "Invalid date format. Please enter as /from yyyy-mm-dd /to yyyy-mm-dd."; + } + + return message; + } +} \ No newline at end of file diff --git a/src/main/java/duke/commands/Mark.java b/src/main/java/duke/commands/Mark.java new file mode 100644 index 0000000000..c66c5a7946 --- /dev/null +++ b/src/main/java/duke/commands/Mark.java @@ -0,0 +1,27 @@ +package duke.commands; + +import duke.helper.Parser; +import duke.helper.Storage; +import duke.helper.TaskList; + +public class Mark extends Command{ + + public Mark(Storage storage, Parser parser, TaskList tasks) { + super(storage, parser, tasks); + } + + @Override + public String execute() { + String message; + try { + int i = Integer.parseInt(parser.getTaskName()) - 1; + message = tasks.markTask(i); + store(); + + } catch (NumberFormatException e) { + message = "need to provide an integer index of task."; + } + + return message; + } +} \ No newline at end of file diff --git a/src/main/java/duke/commands/Sort.java b/src/main/java/duke/commands/Sort.java new file mode 100644 index 0000000000..f7a65cc754 --- /dev/null +++ b/src/main/java/duke/commands/Sort.java @@ -0,0 +1,36 @@ +package duke.commands; + +import duke.helper.Parser; +import duke.helper.Storage; +import duke.helper.TaskList; + +public class Sort extends Command{ + + public Sort(Storage storage, Parser parser, TaskList tasks) { + super(storage, parser, tasks); + } + + @Override + public String execute() { + String message; + if (!(parser.getTaskName().equals("by start date") || parser.getTaskName().equals("by end date"))) { + message = "Sorry, I do not know how you want to sort the tasks"; + } else { + + if (parser.getTaskName().equals("by start date")) { + tasks.sortByStartDate(); + } + + if (parser.getTaskName().equals("by end date")) { + tasks.sortByEndDate(); + } + + store(); + message = "Your list is now sorted!\n\n"; + + message = message + tasks.displayList(); + } + + return message; + } +} \ No newline at end of file diff --git a/src/main/java/duke/commands/Todo.java b/src/main/java/duke/commands/Todo.java new file mode 100644 index 0000000000..f748e3af1a --- /dev/null +++ b/src/main/java/duke/commands/Todo.java @@ -0,0 +1,27 @@ +package duke.commands; + +import duke.helper.Parser; +import duke.helper.Storage; +import duke.helper.Task; +import duke.helper.TaskList; + +public class Todo extends Command{ + + public Todo(Storage storage, Parser parser, TaskList tasks) { + super(storage, parser, tasks); + } + + @Override + public String execute() { + String message; + if (parser.getTaskName().isEmpty()) { + return "Task name cannot be empty"; + } + message = tasks.addTask(new Task(parser.getTaskName(), + 1, "Null", "Null", false)); + store(); + + + return message; + } +} diff --git a/src/main/java/duke/commands/Unmark.java b/src/main/java/duke/commands/Unmark.java new file mode 100644 index 0000000000..481a844e78 --- /dev/null +++ b/src/main/java/duke/commands/Unmark.java @@ -0,0 +1,26 @@ +package duke.commands; + +import duke.helper.Parser; +import duke.helper.Storage; +import duke.helper.TaskList; + +public class Unmark extends Command{ + + public Unmark(Storage storage, Parser parser, TaskList tasks) { + super(storage, parser, tasks); + } + + @Override + public String execute() { + String message; + try { + int i = Integer.parseInt(parser.getTaskName()) - 1; + message = tasks.unmarkTask(i); + store(); + } catch (NumberFormatException e) { + message = "need to provide an integer index of task."; + } + + return message; + } +} \ No newline at end of file diff --git a/src/main/java/duke/gui/DialogBox.java b/src/main/java/duke/gui/DialogBox.java new file mode 100644 index 0000000000..513f9f49bd --- /dev/null +++ b/src/main/java/duke/gui/DialogBox.java @@ -0,0 +1,63 @@ +package duke.gui; + +import java.io.IOException; +import java.util.Collections; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.Circle; + +/** + * An example of a custom control using FXML. + * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label + * containing text from the speaker. + */ +public class DialogBox extends HBox { + @FXML + private Label dialog; + @FXML + private ImageView displayPicture; + + 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/gui/Launcher.java b/src/main/java/duke/gui/Launcher.java new file mode 100644 index 0000000000..b63ebdc174 --- /dev/null +++ b/src/main/java/duke/gui/Launcher.java @@ -0,0 +1,12 @@ +package duke.gui; + +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); + } +} \ No newline at end of file diff --git a/src/main/java/duke/gui/Main.java b/src/main/java/duke/gui/Main.java new file mode 100644 index 0000000000..c516c84e18 --- /dev/null +++ b/src/main/java/duke/gui/Main.java @@ -0,0 +1,34 @@ +package duke.gui; + +import java.io.IOException; + +import duke.Duke; + +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 = new Duke("list.txt", "data"); + + @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); + stage.setTitle("Blus"); + fxmlLoader.getController().setDuke(duke); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/duke/gui/MainWindow.java b/src/main/java/duke/gui/MainWindow.java new file mode 100644 index 0000000000..498041fece --- /dev/null +++ b/src/main/java/duke/gui/MainWindow.java @@ -0,0 +1,60 @@ +package duke.gui; + +import duke.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/A.jpg")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/B.jpg")); + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + } + + public void setDuke(Duke d) { + duke = d; + dialogContainer.getChildren().addAll( + DialogBox.getDukeDialog(d.greet(), dukeImage) + ); + } + + /** + * Creates two dialog boxes, one echoing user input and the other containing duke.Duke's reply and then appends them to + * the dialog container. Clears the user input after processing. + */ + @FXML + private void handleUserInput() { + String input = userInput.getText(); + 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/helper/Parser.java b/src/main/java/duke/helper/Parser.java new file mode 100644 index 0000000000..4bccbac81c --- /dev/null +++ b/src/main/java/duke/helper/Parser.java @@ -0,0 +1,109 @@ +package duke.helper; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +public class Parser { + private String command = ""; + private String taskName = ""; + private String firstEnteredTime = ""; + private String secondEnteredTime = ""; + + + public Parser() { + + } + + + /** + * Process the user input to extract relevant information and store them correctly for executing user command. + * @param userInput the input user command + */ + public void processUserCommand(String userInput) { + + List tokens = tokenize(userInput); + command = tokens.get(0); + + List attributes = tokensToAttributes(tokens); + + switch (attributes.size()) { + case 3: + secondEnteredTime = attributes.get(2); + + case 2: + firstEnteredTime = attributes.get(1); + + case 1: + taskName = attributes.get(0); + + } + + } + + + /** + * Converts user input into a list of tokens. + * + * @param userInput The user input to be converted into list of tokens. + * @return The list of tokens. + */ + public List tokenize(String userInput) { + List tokens = new ArrayList<>(); + Scanner lineScanner = new Scanner(userInput); + while (lineScanner.hasNext()) { + + String token = lineScanner.next(); + tokens.add(token); + + } + lineScanner.close(); + return tokens; + } + + /** + * Converts a list of tokens into a list of attributes. + * + * @param tokens The list of tokens to be converted into list of attributes. + * @return The list of attributes. + */ + public List tokensToAttributes(List tokens) { + List attributes = new ArrayList<>(); + StringBuilder attributeName = new StringBuilder(); + + for (int a = 1; a < tokens.size(); a++) { + String element = tokens.get(a); + if (element.charAt(0) == '/') { + attributes.add(attributeName.toString()); + attributeName = new StringBuilder(); + } else { + if (attributeName.length() == 0) { + attributeName.append(element); + } else { + attributeName.append(" ").append(element); + } + + + } + } + + attributes.add(attributeName.toString()); + return attributes; + } + + + public String getCommand() { + return command; + } + + public String getTaskName() { + return taskName; + } + + public String getFirstEnteredTime() { + return firstEnteredTime; + } + + public String getSecondEnteredTime() { + return secondEnteredTime; + } +} diff --git a/src/main/java/duke/helper/Storage.java b/src/main/java/duke/helper/Storage.java new file mode 100644 index 0000000000..212ed14c3d --- /dev/null +++ b/src/main/java/duke/helper/Storage.java @@ -0,0 +1,224 @@ +package duke.helper; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +public class Storage { + + private String fileName; + private String dirName; + private String filePath; + + private int listPointer; + + + //private methods below + + /** + * Sets up the background for storage. + * + */ + private void backgroundSetUp() { + + File dir = new File(this.dirName); + if (!(dir.exists())) { + if (dir.mkdir()) { + System.out.println("Directory '" + this.dirName + "' created."); + } else { + System.err.println("Failed to create directory '" + this.dirName + "'."); + } + } + + + File file = new File(dir, this.fileName); + + if (!(file.exists())) { + try { + if (file.createNewFile()) { + System.out.println("File '" + this.fileName + "' created in directory '" + this.dirName + "'."); + } else { + System.err.println("Failed to create file '" + this.fileName + "' in directory '" + this.dirName + "'."); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + + + + //public methods below + + public Storage (String fileName, String dirName) { + this.fileName = fileName; + this.dirName = dirName; + this.filePath = this.dirName + "/" + this.fileName; + + backgroundSetUp(); + + } + + + /** + * Converts the content in the file into a list of tasks. + * + * @return The list of tasks. + */ + public Task[] loadList() { + Task[] userList = new Task[100]; + int positionPointer = 0; + + + Path path = Paths.get(this.filePath); + try { + Scanner fileScanner = new Scanner(path); + while(fileScanner.hasNextLine()){ + + // Record format: "Type | Status | Name | StartTime(optional) | EndTime(optional)" + // example: "D | 0 | return book | 2023-09-04" + // "0" for not done and "1" for done + + String line = fileScanner.nextLine(); + + List record = readRecord(readLine(line)); + + Task task = recordToTask(record); + userList[positionPointer] = task; + + positionPointer++; + + + } + fileScanner.close(); + + } catch (IOException e) { + throw new RuntimeException(e); + } + + this.listPointer = positionPointer; + + + return userList; + } + + /** + * Converts a line into a list of tokens. + * + * @param line The line to be converted into list of tokens. + * @return The list of tokens. + */ + private List readLine(String line) { + List tokens = new ArrayList<>(); + Scanner lineScanner = new Scanner(line); + while (lineScanner.hasNext()) { + + String token = lineScanner.next(); + tokens.add(token); + + } + lineScanner.close(); + + return tokens; + } + + + /** + * Converts a list of tokens into a record. + * + * @param tokens The list of tokens to be converted into record. + * @return The converted record. + */ + public List readRecord(List tokens) { + + List record = new ArrayList<>(); + StringBuilder attributeName = new StringBuilder(); + + for (String element : tokens) { + if (element.equals("|")) { + record.add(attributeName.toString()); + attributeName = new StringBuilder(); + } else { + if (attributeName.length() == 0) { + attributeName.append(element); + } else { + attributeName.append(" ").append(element); + } + + + } + } + + record.add(attributeName.toString()); + + return record; + + } + + /** + * Converts a record into a Task object. + * + * @param record The record to be converted into Task object. + * @return The converted Task object. + */ + public Task recordToTask(List record) throws IllegalStateException { + boolean isDone = record.get(1).equals("1"); + Task task; + + switch (record.get(0)) { + case "T": + task = new Task(record.get(2), 1, "Null", "Null", isDone); + break; + + case "D": + task = new Task(record.get(2), 2, "Null", record.get(3), isDone); + break; + + case "E": + task = new Task(record.get(2), 3, record.get(3), record.get(4), isDone); + break; + + default: + throw new IllegalStateException("Unexpected value: " + record.get(0)); + } + + return task; + } + + /** + * Saves the list back to file. + * + * @param userList The list of tasks to store. + * @param numberOfElements The number of valid tasks in the list. + */ + public void saveList(Task[] userList, int numberOfElements) { + + try (FileWriter writer = new FileWriter(filePath)) { + for (int i = 0; i < numberOfElements; i++) { + writer.write(userList[i].getStringForRecordingInTextFile()); + writer.write("\n"); + } + + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + public int getListPointer() { + return listPointer; + } + + + + + + + + +} diff --git a/src/main/java/duke/helper/Task.java b/src/main/java/duke/helper/Task.java new file mode 100644 index 0000000000..aa10e72643 --- /dev/null +++ b/src/main/java/duke/helper/Task.java @@ -0,0 +1,144 @@ +package duke.helper; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + + +public class Task { + private boolean isDone; + + private int typeOfTask; + + private String taskName; + + private LocalDate startDate = LocalDate.MAX; + private LocalDate endDate = LocalDate.MAX; + + + public Task(String taskName, int typeOfTask, String startDate, String endDate, boolean isDone) { + this.taskName = taskName; + this.typeOfTask = typeOfTask; + + switch (this.typeOfTask) { + case 1: + break; + case 2: + this.endDate = LocalDate.parse(endDate); + break; + case 3: + this.startDate = LocalDate.parse(startDate); + this.endDate = LocalDate.parse(endDate); + break; + } + + + this.isDone = isDone; + + } + + public void markDone() { + isDone = true; + } + + public void unmarkDone() { + isDone = false; + } + + public LocalDate getStartDate() { + return startDate; + } + + public LocalDate getEndDate() { + return endDate; + } + + public boolean getCompletionStatus() { + return isDone; + } + + /** + * Returns the string representing how the Task object should be displayed. + * @return String representation of the Task object. + */ + public String display() { + String description = "["; + switch (this.typeOfTask) { + case 1: + description = description + "T]"; + break; + case 2: + description = description + "D]"; + break; + case 3: + description = description + "E]"; + break; + } + if (isDone) { + description = description + "[X] " + taskName; + } else { + description = description + "[ ] " + taskName; + } + + if (this.typeOfTask == 2) { + description = description + " (by: " + endDate.format(DateTimeFormatter.ofPattern("MM-dd-yyyy")) + ")"; + } + + if (this.typeOfTask == 3) { + description = description + " (from: " + startDate.format(DateTimeFormatter.ofPattern("MM-dd-yyyy")) + + " to: " + endDate.format(DateTimeFormatter.ofPattern("MM-dd-yyyy")) + ")"; + } + + + + return description; + } + + /** + * Returns the string representing how the Task object should be stored. + * @return String representation of the Task object. + */ + public String getStringForRecordingInTextFile() { + // Record format: "typeOfTask | Status | Name | StartTime(optional) | EndTime(optional)" + // example: "D | 0 | return book | June 6th" + // "0" for not done and "1" for done + + String description = ""; + switch (this.typeOfTask) { + case 1: + description = description + "T | "; + break; + case 2: + description = description + "D | "; + break; + case 3: + description = description + "E | "; + break; + } + if (isDone) { + description = description + "1 | " + taskName; + } else { + description = description + "0 | " + taskName; + } + + if (this.typeOfTask == 2) { + description = description + " | " + endDate.toString(); + } + + if (this.typeOfTask == 3) { + description = description + " | " + startDate.toString() + " | " + endDate.toString(); + } + + + + return description; + } + + public String getTaskName() { + return taskName; + } + + public int getTypeOfTask() { + return typeOfTask; + } +} + + diff --git a/src/main/java/duke/helper/TaskList.java b/src/main/java/duke/helper/TaskList.java new file mode 100644 index 0000000000..7e712e10bd --- /dev/null +++ b/src/main/java/duke/helper/TaskList.java @@ -0,0 +1,247 @@ +package duke.helper; + + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +public class TaskList { + private Task[] userList; + private int userListPointer; + + + + + public TaskList(Task[] userList, int userListPointer) { + this.userList = userList; + this.userListPointer = userListPointer; + + } + + public TaskList() { + this.userList = new Task[100]; + this.userListPointer = 0; + } + + /** + * Adds a task into the task list. + * + * @param task The task to add. + * @return A message of successfully adding the task. + */ + public String addTask(Task task) { + userList[userListPointer] = task; + userListPointer++; + String message = "Got it. I've added this task:\n" + task.display(); + message = message + "\nNow you have " + userListPointer + " tasks in the list."; + return message; + + + } + + /** + * Deletes a task at given position. + * + * @param position The position of the task to be deleted. + * @return A message of the result of the process. + */ + public String deleteTask(int position) { + + if (position < 0 || position >= userListPointer) { + return ("Invalid index."); + } else { + + String message = "Noted. I've removed this task:\n" + userList[position].display(); + + + Task[] newUserList = new Task[100]; + + for (int a = 0, k = 0; a < userListPointer; a++) { + + // if the index is + // the removal element index + if (a == position) { + continue; + } + + // if the index is not + // the removal element index + newUserList[k++] = userList[a]; + } + + userListPointer--; + + userList = newUserList; + + message = message + "\nNow you have " + userListPointer + " tasks in the list."; + return message; + + } + } + + /** + * Marks a task at given position as done. + * + * @param position The position of the task to be marked. + * @return A message of the result of the process. + */ + public String markTask(int position) { + if (position < 0 || position >= userListPointer) { + return "Invalid index."; + } else { + + userList[position].markDone(); + + assert userList[position].getCompletionStatus(); //target task should be marked as done + + int index = position + 1; + return "Following task is marked as done:\n" + index + ". " + + userList[position].display(); + + } + } + + /** + * Marks a task at given position as undone. + * + * @param position The position of the task to be marked. + * @return A message of the result of the process. + */ + public String unmarkTask(int position) { + if (position < 0 || position >= userListPointer) { + return "Invalid index."; + } else { + + userList[position].unmarkDone(); + + assert !userList[position].getCompletionStatus();//target task should be marked as undone + + int index = position + 1; + return "Following task is marked as undone:\n" + index + ". " + + userList[position].display(); + + } + } + + public Task[] getUserList() { + return userList; + } + + public int getUserListPointer() { + return userListPointer; + } + + /** + * Returns the String containing all the information in the list. + * @return String representation of the content in the list. + */ + public String displayList() { + if (userListPointer < 1) { + return "No items in the list yet"; + } else { + StringBuilder message = new StringBuilder(); + for (int i = 0; i < userListPointer; i++) { + int num = i + 1; + message.append(num).append(". ").append(userList[i].display()).append("\n"); + + + } + return message.toString(); + } + } + + /** + * Finds all the tasks in the task list with given key word in their names. + * + * @param keyWords The key word for searching. + * @return A string representation of all the tasks found. + */ + public String findTask(String keyWords) { + if (userListPointer < 1) { + return "No items in the list yet"; + } else { + int num = 1; + StringBuilder message = new StringBuilder(); + for (int i = 0; i < userListPointer; i++) { + if (userList[i].getTaskName().toLowerCase().contains(keyWords.toLowerCase())) { + message.append(num).append(". ").append(userList[i].display()).append("\n"); + num++; + } + + } + if (num == 1) { + return "No matches found"; + } else { + return "Here are the matching tasks in your list:\n" + message; + } + + } + } + + + /** + * Shows statistics about the task list. + */ + public String showTaskStatistics() { + if (userListPointer < 1) { + return "No items in the list yet"; + } else { + int numTodo = 0; + int numDeadLine = 0; + int numEvent = 0; + + for (int i = 0; i < userListPointer; i++) { + int type = userList[i].getTypeOfTask(); + if (type == 1) { + numTodo++; + } + if (type == 2) { + numDeadLine++; + } + if (type == 3) { + numEvent++; + } + + + + } + + assert numTodo <= userListPointer;//number of todo tasks should not exceed the total number of tasks + assert numDeadLine <= userListPointer;//number of deadline tasks should not exceed the total number of tasks + assert numEvent <= userListPointer;//number of event tasks should not exceed the total number of tasks + + return "You have:\n" + "~ " + numTodo + " todo.\n" + "~ " + numDeadLine + " deadline.\n" + + "~ " + numEvent + " event.\n"; + + + + } + } + + /** + * Sorts the list based on start dates. + */ + public void sortByStartDate() { + List tasks = new ArrayList<>(); + for (int i = 0; i < userListPointer; i++) { + tasks.add(userList[i]); + } + tasks.sort(Comparator.comparing(Task::getStartDate)); + for (int i = 0; i < userListPointer; i++) { + userList[i] = tasks.get(i); + } + } + + /** + * Sorts the list based on end dates. + */ + public void sortByEndDate() { + List tasks = new ArrayList<>(); + for (int i = 0; i < userListPointer; i++) { + tasks.add(userList[i]); + } + tasks.sort(Comparator.comparing(Task::getEndDate)); + for (int i = 0; i < userListPointer; i++) { + userList[i] = tasks.get(i); + } + } +} diff --git a/src/main/java/duke/helper/Ui.java b/src/main/java/duke/helper/Ui.java new file mode 100644 index 0000000000..5dbc2c6150 --- /dev/null +++ b/src/main/java/duke/helper/Ui.java @@ -0,0 +1,34 @@ +package duke.helper; + +public class Ui { + + private String MyName; + + public Ui(String MyName) { + this.MyName = MyName; + } + + /** + * print message in a specific format + * @param message the original message to be printed + */ + public void speak(String message) { + System.out.println("---------------------------"); + System.out.println(message); + System.out.println("---------------------------"); + } + + public String greet() { + + return "Hello! I'm " + MyName + "\n" + "What can I do for you?"; + + } + + public String exit() { + + return "Bye. Hope to see you again soon!"; + + } + + +} diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..1627c7e6cd --- /dev/null +++ b/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: duke.gui.Launcher + diff --git a/src/main/resources/images/A.jpg b/src/main/resources/images/A.jpg new file mode 100644 index 0000000000..2a0e886d02 Binary files /dev/null and b/src/main/resources/images/A.jpg differ diff --git a/src/main/resources/images/B.jpg b/src/main/resources/images/B.jpg new file mode 100644 index 0000000000..b16dcdedcf Binary files /dev/null and b/src/main/resources/images/B.jpg differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..394e0901b6 --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..8c26537855 --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + +