diff --git a/.gitignore b/.gitignore index 2873e189e1..2b2f935869 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,7 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT + +# Temp files +src/main/java/temp.java + diff --git a/README.md b/README.md index 90aa7f092a..e4af28146f 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,49 @@ -# Duke project template - -This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. - -## Setting up in Intellij - -Prerequisites: JDK 17, update Intellij to the most recent version. - -1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project first) -1. Open the project into Intellij as follows: - 1. Click `Open`. - 1. Select the project directory, and click `OK`. - 1. If there are any further prompts, accept the defaults. -1. Configure the project to use **JDK 17** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
- In the same dialog, set the **Project language level** field to the `SDK default` option. -3. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: - ``` - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - ``` +# John Cena Todo List Application :tada: + +Welcome to the **John Cena** Todo List application! This app helps you manage your tasks efficiently with commands like `todo`, `deadline`, `event`, `find`, `after`, and more. It automatically saves your tasks, ensuring you never lose track of your to-dos. + +## 🚀 Features + +- **Add Tasks**: Create `todo`, `deadline`, `event`, and `after` tasks. +- **Manage Tasks**: Mark, unmark, and delete tasks. +- **Search Tasks**: Find tasks by keyword. +- **View Tasks by Date**: List tasks occurring on a specific date. +- **Automatic Saving**: Tasks are saved automatically to prevent data loss. +- **User-Friendly GUI**: Intuitive interface for easy interaction. + +## 🛠 Installation + +Follow these simple steps to set up the **John Cena** Todo List application: + +1. **Download the JAR file** from the [Releases](https://github.com/ishan-agarwal-05/ip/releases) section of this repository. + +2. **Run the application** using the following command: + + ```bash + java -jar johncena.jar + ``` + +--- + +## 📚 Acknowledgements + +- **Java**: The primary programming language used for this project. +- **IntelliJ IDEA**: The IDE used for development. +- **GitHub**: For hosting the project repository. +- **OpenAI**: For providing AI assistance. +- **JavaFX**: For the GUI framework. +- **JUnit 5**: For testing the application. +- **Gradle**: For building the project. +- **Github Copilot**: For generating code snippets and javadoc comments. +- **AB3 User Guide and blackpanther9229**: For the template of user guide. + + +## 🤖 Special Acknowledgment for GitHub Copilot + +Throughout the development of this project, GitHub Copilot was used for code autofill and suggestions. + +For example: +- The `Deadline` class was coded manually, but the `Event` class has similarities due to Copilot's autofill suggestions. +- Many command classes, such as `TodoCommand`, `DeadlineCommand`, and `EventCommand`, were generated with significant help from Copilot. + +This extensive use of AI assistance has helped streamline the development process, but it also means that large segments of the code are AI-generated and might resemble code from other projects. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..579f305bcf --- /dev/null +++ b/build.gradle @@ -0,0 +1,63 @@ +plugins { + id 'java' + id 'application' + id 'com.github.johnrengelman.shadow' version '7.1.2' + id 'checkstyle' + } + +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("johncena.Launcher") +} + +shadowJar { + archiveBaseName = "JohnCena" + archiveClassifier = null + archiveFileName = "JohnCena.jar" +} + +run{ + standardInput = System.in +} + +checkstyle { + toolVersion = '10.2' +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..eb761a9b9a --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..39efb6e4ac --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/data/CenaTaskList.txt b/data/CenaTaskList.txt new file mode 100644 index 0000000000..8f590a1a90 --- /dev/null +++ b/data/CenaTaskList.txt @@ -0,0 +1,8 @@ +T | 0 | eat dinner +E | 0 | birthday | 2024-09-22 0001 ~ 2024-09-22 2359 +A | 0 | party | 2024-09-25 1600 +T | 0 | eat lunch +D | 0 | submit report | 2024-09-22 1600 +T | 0 | eat dinner +T | 0 | fly +T | 0 | testing diff --git a/docs/README.md b/docs/README.md index 47b9f984f7..9b650592ef 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,30 +1,334 @@ -# Duke User Guide +# John Cena Task Manager User Guide -// Update the title above to match the actual product name +Meet John Cena Task Manager, your dedicated assistant for managing daily tasks, deadlines, and events. Designed to streamline your schedule and keep you on top of your commitments, John Cena Task Manager makes organizing your life simpler and more efficient. -// Product screenshot goes here +- [Quick Start](#quick-start) +- [Features and Commands](#john-cena-task-manager-supports-the-following-features) + - [Adding Todo tasks: `todo`](#1-adding-a-to-do-task) + - [Adding Deadline tasks: `deadline`](#2-adding-a-deadline) + - [Adding Event tasks: `event`](#3-adding-an-event) + - [Adding tasks after a certain date: `after`](#4-adding-a-task-to-be-done-after-a-certain-date) + - [Finding tasks on a certain date: `on`](#5-find-tasks-on-a-certain-date) + - [Finding matching tasks: `find`](#6-find-matching-tasks) + - [Marking task as done: `mark`](#7-mark-a-task-as-done) + - [Marking task as not done: `unmark`](#8-unmark-a-task-mark-a-task-as-not-done) + - [Deleting a task: `delete`](#9-delete-a-task) + - [Viewing the task list: `list`](#10-view-all-tasks) + - [Hello Interaction: `hello`](#11-hello-interaction) + - [List all commands: `help`](#12-list-all-commands) + - [Exit the program: `bye`](#13-exit-the-program) +- [FAQ](#faq) +- [Known Issues](#known-issues) +- [Command Summary](#command-summary) -// Product intro goes here +# Quick Start -## Adding deadlines +1. Ensure you have Java `17` or above installed on your computer. -// Describe the action and its outcome. +1. Download the latest `.jar` file from [here](https://github.com/ishan-agarwal-05/ip/releases). -// Give examples of usage +1. Copy the file to the folder you want to use as the _home folder_ for your John Cena Task Manager. -Example: `keyword (optional arguments)` +1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar JohnCena.jar` command to run the application.
+ A GUI similar to the below should appear in a few seconds.
+ ![Ui](Ui.png) -// A description of the expected outcome goes here +1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will show you a list of commands.
+ Some example commands you can try: + * `list` : Lists all tasks. + + * `Todo swim` : Add a new `Todo` task to swim in the current list. + + * `delete 3` : Deletes the 3rd task shown in the current list. + + * `bye` : Exits the app. + +1. Refer to the [Features](#john-cena-task-manager-supports-the-following-features) below for details of each command. + +-------------------------------------------------------------------------------------------------------------------- + +## John Cena Task Manager Supports the Following Features: + +> [!NOTE] +> Notes about the syntax format: +> - Words in single quotation mark `''` are the parameters to be supplied by the user.
+ The parameters should follow the guide in the single quotation mark.
+ e.g. in `Todo 'Description'`, `'Description'` is a parameter which can be used as `Todo Sleep`. +> - Extra arguments in most of the command types will be interpreted as a wrong format error. +> - The date and time format should be in `yyyy-mm-dd HHmm` format. +> - The commands are case-sensitive. e.g. `todo` will work, but `Todo` will not work. + +### 1. Adding a To-do task + +To add a To-do to the storage, use the following format: + +`todo 'Description'` + +Example: `todo eat dinner` + +``` + Alright, Champ! I've added this task: + [T] [ ] eat dinner + Now you have 1 tasks in the list. Keep hustling! +``` + +### 2. Adding a Deadline + +To add a task with a deadline to the list, use the following format: + +`deadline 'Description' /by 'yyyy-mm-dd HHmm'` + +Example: `deadline CS2100 /by 2024-09-25 1600` + +Response: +``` + Got it. I've added this task: + [D] [ ] CS2100 (by: 2024-09-25 1600) + Now you have 2 tasks in the list. +``` + +### 3. Adding an Event + +To add an Event to the list, use the following format: + +`event 'Description' /from 'yyyy-mm-dd HHmm' /to 'yyyy-mm-dd HHmm'` + +Example: `event birthday /from 2024-09-22 0001 /to 2024-09-22 2359` + +Response: ``` -expected output + Got it. I've added this task: + [E] [ ] birthday (from: 2024-09-22 0001 to: 2024-09-22 2359) + Now you have 3 tasks in the list. Keep hustling! +``` + +### 4. Adding a task to be done after a certain date + +To add a task to be done after a certain date, use the following format: + +`after 'Description' /after 'yyyy-mm-dd HHmm'` + +Example: `after party /after 2024-09-25 1600` + +Response: +``` + Got it. I've added this task: + [A] [ ] party (after: 2024-09-25 1600) + Now you have 4 tasks in the list. +``` + +### 5. Find tasks on a certain date + +To find tasks on a certain date, use the following format: + +`on 'yyyy-mm-dd'` + +Example: +`on 2024-09-22` + +Response: +``` + Alright, Champ! Here are the tasks on Sep 22 2024: + 1. [E] [ ] birthday (from: 2024-09-22 0001 to: 2024-09-22 2359) + 5. [D] [ ] submit report (by: 2024-09-22 1600) +``` + +### 6. Find matching tasks + +To find matching tasks for some keywords, use the following format: + +`find 'one or more keywords'` + +* The search is case-sensitive. e.g `Eat` will match `eat` +* Non-space words will be used to search. e.g `eat lunch` will be divided into `eat` and `lunch` to search +* Partial words will be matched e.g. `lun` will match `lunch` + +Example: +`find eat` + +Response: +``` + Here are the matching tasks in your list: + 1. [T] [ ] eat dinner + 5. [T] [ ] eat lunch +``` + +Example: +`find eat lun` + +Response: ``` + Here are the matching tasks in your list: + 5. [T] [ ] eat lunch +``` + +The response can be interpreted as follows: +- Event type (`T` is a Todo, `E` is an Event, `D` is a Deadline, 'A' is an After) +- Status (`[X]` for completed, `[ ]` otherwise) +- Description +- Start and end dates (if applicable) in `yyyy-mm-dd HHmm` format + +### 7. Mark a task as done + +To mark a task as done, use the following format: + +`mark 'ordinal number of the task'` + +* Mark at the specified `index`. +* The index refers to the index number shown in the displayed tasks list. +* The index **must be a positive integer** 1, 2, 3, … +* The index must be valid, i.e., **within the range of the current task list**. +* The task will be marked as done with `[X]`. + +Example: +`mark 2` + +Response: +``` + You did it, Champ! I've marked this task as done: + [D] [X] CS2100 (by: 2024-09-25 1600) +``` + +### 8. Unmark a task/ Mark a task as not done + +To unmark a task as done, use the following format: + +`unmark 'ordinal number of the task'` + +* Unmark at the specified `index`. +* The index refers to the index number shown in the displayed tasks list. +* The index **must be a positive integer** 1, 2, 3, … +* The index must be valid, i.e., **within the range of the current task list**. +* The task will be marked as not done with `[ ]`. + +Example: +`unmark 2` + +Response: +``` + Alright, Champ! I've marked this task as not done yet: + [D] [ ] CS2100 (by: 2024-09-25 1600) +``` + +### 9. Delete a task + +To delete a task, use the following format: + +`delete 'index'` + +* Deletes the task at the specified `index`. +* The index refers to the index number shown in the displayed tasks list. +* The index **must be a positive integer** 1, 2, 3, … +* The index must be valid, i.e., **within the range of the current task list**. +* The task will be removed from the list. + +Example: +`delete 2` + +Response: +``` + Noted. I've removed this task. Now, you can't see me and neither can you see that task: + [D] [ ] CS2100 (by: 2024-09-25 1600) + Now you have 4 tasks in the list. +``` +> [!NOTE] +> After a task is deleted, the remaining tasks will be automatically renumbered.
+> E.g: task list has 3 tasks with index 1, 2, 3, after deleting task 2, old task 3 will become new task 2. + +### 10. View all tasks + +To view all tasks, use the following command: + +`list` + +Example: +`list` + +Response: +``` + Alright, Champ! Here are the tasks in your list: + 1. [T] [ ] eat dinner + 2. [E] [ ] birthday (from: 2024-09-22 0001 to: 2024-09-22 2359) + 3. [A] [ ] party (after: 2024-09-25 1600) + 4. [T] [ ] eat lunch +``` + +### 11. Hello Interaction + +To say hi to the chatbot, use anyone of the following command: + +`hello`, `hi`, `hey`, `yo`, `sup` + +The chatbot will respond with a greeting message. + +Example: +`hi` + +Response: +``` +Hello from +John Cena +You can't see me! But I'm here to help. What can I do for you today? +``` +### 12. List all commands + +To view all commands, use anyone of the following command: + +`help`, `commands`, `command`, `cmds`, `cmd` + +The chatbot will list all the commands available with their formats and examples. + +Example: +`help` + +Response: +``` +Here are the commands you can use: + bye – Exits the program + list – Lists all tasks + mark [task number] – Marks a task as done + unmark [task number] – Marks a task as not done + delete [task number] – Deletes a task + todo [description] – Adds a todo task + deadline [description] /by [due date] – Adds a deadline task + event [description] /from [start date] /to [end date] – Adds an event task + find [keyword] – Finds tasks with a specific keyword + after [description] /after [date] – Adds a task that you will do after a specific date + on [date] – Lists all tasks on a specific date + hello – Displays the welcome message + help – Displays the list of commands +``` +### 13. Exit the program + +To exit the program, use the following command: + +`bye` -## Feature ABC +The program will close. -// Feature details +-------------------------------------------------------------------------------------------------------------------- +## Known Issues +1. **Command Overwriting**: If users attempt to add a task with the same description as an existing one, the app does not prevent duplicates. Users should manage their tasks accordingly. +2. **Not Case-Sensitive**: The app is not case-sensitive, so users should ensure they input commands in lowercase. +3. **Date and Time Format**: The app only accepts dates and times in the `yyyy-mm-dd HHmm` format. Users should ensure they input dates and times correctly. -## Feature XYZ +-------------------------------------------------------------------------------------------------------------------- +## Command Summary -// Feature details \ No newline at end of file +| Action | Format, Examples | +|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| +| **Add To-do** | `todo 'Description'`
e.g., `todo eat dinner` | +| **Add Deadline** | `deadline 'Description' /by 'yyyy-mm-dd HHmm'`
e.g., `deadline CS2100 /by 2024-09-25 1600` | +| **Add Event** | `event 'Description' /from 'yyyy-mm-dd HHmm' /to 'yyyy-mm-dd HHmm'`
e.g., `event birthday /from 2024-09-22 0001 /to 2024-09-22 2359` | +| **Add Task After a Date** | `after 'Description' /after 'yyyy-mm-dd HHmm'`
e.g., `after party /after 2024-09-25 1600` | +| **Find Tasks on a Date** | `on 'yyyy-mm-dd'`
e.g., `on 2024-09-22` | +| **Find Matching Tasks** | `find 'one or more keywords'`
e.g., `find eat lun` | +| **Mark Task as Done** | `mark 'ordinal number of the task'`
e.g., `mark 2` | +| **Unmark Task (Mark as Not Done)** | `unmark 'ordinal number of the task'`
e.g., `unmark 2` | +| **Delete Task** | `delete 'ordinal number of the task'`
e.g., `delete 2` | +| **View All Tasks** | `list` | +| **Hello Interaction** | `hello`, `hi`, `hey`, `yo`, `sup` | +| **List All Commands** | `help`, `commands`, `command`, `cmds`, `cmd` | +| **Exit the Program** | `bye` | \ No newline at end of file diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..f597583a41 Binary files /dev/null and b/docs/Ui.png differ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..033e24c4cd Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..66c01cfeba --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..fcb6fca147 --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..6689b85bee --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334cc..0000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} diff --git a/src/main/java/johncena/JohnCena.java b/src/main/java/johncena/JohnCena.java new file mode 100644 index 0000000000..1a2ccdf0e0 --- /dev/null +++ b/src/main/java/johncena/JohnCena.java @@ -0,0 +1,48 @@ +package johncena; + +import java.util.ArrayList; + +import johncena.commands.Command; +import johncena.commands.HelloCommand; +import johncena.exceptions.CenaException; +import johncena.parser.InputHandler; +import johncena.storage.Storage; +import johncena.tasks.Task; + + + +/** + * The {@code JohnCena} class is the main class of the John Cena program. + */ +public class JohnCena { + private static ArrayList tasks = new ArrayList<>(); + private static InputHandler inputHandler; + + /** + * Initializes the John Cena program. + */ + public JohnCena() { + tasks = Storage.loadTasks(); + inputHandler = new InputHandler(tasks); + HelloCommand helloCommand = new HelloCommand(); + helloCommand.execute(); + } + + + /** + * Generates a response for the user's chat message. + * + * @param input the user input + * @return the response + */ + public String getResponse(String input) { + assert input != null : "Input should not be null"; + try { + Command command = inputHandler.handleInput(input); + return command.execute(); + } catch (CenaException e) { + return "OOPS!!! " + e.getMessage(); + } + } + +} diff --git a/src/main/java/johncena/Launcher.java b/src/main/java/johncena/Launcher.java new file mode 100644 index 0000000000..d4dcd1ebc0 --- /dev/null +++ b/src/main/java/johncena/Launcher.java @@ -0,0 +1,14 @@ +package johncena; + + +import javafx.application.Application; +import johncena.gui.Main; + +/** + * 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/johncena/art/Logo.java b/src/main/java/johncena/art/Logo.java new file mode 100644 index 0000000000..193a276a34 --- /dev/null +++ b/src/main/java/johncena/art/Logo.java @@ -0,0 +1,21 @@ + +package johncena.art; + + +/** + * The {@code Logo} class provides a static method to retrieve the ASCII art logo + * for the John Cena Task Manager application. + */ +public class Logo { + private static final String logo = "John Cena"; + + + /** + * Returns the ASCII art logo for the John Cena Task Manager application. + * + * @return the ASCII art logo + */ + public static String getLogo() { + return logo; + } +} diff --git a/src/main/java/johncena/commands/AfterCommand.java b/src/main/java/johncena/commands/AfterCommand.java new file mode 100644 index 0000000000..554c480ddd --- /dev/null +++ b/src/main/java/johncena/commands/AfterCommand.java @@ -0,0 +1,59 @@ +package johncena.commands; + +import java.util.ArrayList; + +import johncena.exceptions.CenaInvalidAfterException; +import johncena.storage.Storage; +import johncena.tasks.After; +import johncena.tasks.Task; + +/** + * The {@code AfterCommand} class implements the {@code Command} interface and provides + * the functionality to execute the "after" command, which adds an after task to the task list. + */ +public class AfterCommand implements Command { + + private ArrayList tasks; + private String description; + private String after; + + /** + * Constructs a new {@code AfterCommand} with the specified task list, description, and after time. + * + * @param tasks the list of tasks + * @param description the description of the after task + * @param after the time after which the task should be done + */ + public AfterCommand(ArrayList tasks, String description, String after) { + this.tasks = tasks; + this.description = description; + this.after = after; + } + + /** + * Executes the "after" command. Adds an after task to the task list. + * + * @throws CenaInvalidAfterException if the description for the after task is incorrect + */ + @Override + public String execute() { + assert description != null : "Description should not be null"; + assert after != null : "After time should not be null"; + try { + if (description.isEmpty() || after.isEmpty()) { + throw new CenaInvalidAfterException("Incorrect description for after task. It should contain /after."); + } + Task task = new After(description, after); + tasks.add(task); + Storage.saveTasks(tasks); + + StringBuilder sb = new StringBuilder(); + sb.append(" Got it. I've added this task:\n"); + sb.append(" ").append(task).append("\n"); + sb.append(" Now you have ").append(tasks.size()).append(" tasks in the list.\n"); + return sb.toString(); + } catch (CenaInvalidAfterException e) { + return e.getMessage(); + } + } +} diff --git a/src/main/java/johncena/commands/ByeCommand.java b/src/main/java/johncena/commands/ByeCommand.java new file mode 100644 index 0000000000..35149f6d27 --- /dev/null +++ b/src/main/java/johncena/commands/ByeCommand.java @@ -0,0 +1,28 @@ +package johncena.commands; + +import johncena.art.Logo; + + +/** + * The {@code ByeCommand} class implements the {@code Command} interface and provides + * the functionality to execute the "bye" command, which prints a farewell message + * and the ASCII art logo, then exits the application. + */ +public class ByeCommand implements Command { + + + /** + * Executes the "bye" command. Prints a farewell message and the ASCII art logo, + * then exits the application. + */ + @Override + public String execute() { + StringBuilder sb = new StringBuilder(); + sb.append(" You can't see me, but I'll be back!\n"); + sb.append("This was\n"); + sb.append(Logo.getLogo()).append("\n"); + sb.append("signing off!\n"); + System.exit(0); + return sb.toString(); + } +} diff --git a/src/main/java/johncena/commands/Command.java b/src/main/java/johncena/commands/Command.java new file mode 100644 index 0000000000..ddffffd905 --- /dev/null +++ b/src/main/java/johncena/commands/Command.java @@ -0,0 +1,18 @@ +package johncena.commands; + +import johncena.exceptions.CenaException; + + +/** + * The {@code Command} interface provides a method to execute a command. + * Implementing classes should provide the specific implementation of the command. + */ +public interface Command { + + /** + * Executes the command. + * + * @throws CenaException if an error occurs during the execution of the command + */ + String execute() throws CenaException; +} diff --git a/src/main/java/johncena/commands/DeadlineCommand.java b/src/main/java/johncena/commands/DeadlineCommand.java new file mode 100644 index 0000000000..a7522ff122 --- /dev/null +++ b/src/main/java/johncena/commands/DeadlineCommand.java @@ -0,0 +1,61 @@ +package johncena.commands; + +import java.util.ArrayList; + +import johncena.exceptions.CenaInvalidDeadlineException; +import johncena.storage.Storage; +import johncena.tasks.Deadline; +import johncena.tasks.Task; + +/** + * The {@code DeadlineCommand} class implements the {@code Command} interface and provides + * the functionality to execute the "deadline" command, which adds a deadline task to the task list. + */ +public class DeadlineCommand implements Command { + + private ArrayList tasks; + private String description; + private String by; + + /** + * Constructs a new {@code DeadlineCommand} with the specified task list, description, and deadline. + * + * @param tasks the list of tasks + * @param description the description of the deadline task + * @param by the deadline for the task + */ + public DeadlineCommand(ArrayList tasks, String description, String by) { + this.tasks = tasks; + this.description = description; + this.by = by; + } + + /** + * Executes the "deadline" command. Adds a deadline task to the task list. + * + * @throws CenaInvalidDeadlineException if the description for the deadline is incorrect + */ + @Override + public String execute() { + assert description != null : "Description should not be null"; + assert by != null : "Deadline should not be null"; + try { + if (description.isEmpty() || by.isEmpty()) { + throw new CenaInvalidDeadlineException("Incorrect description for deadline. It should contain" + + " only /by."); + } + Task task = new Deadline(description, by); + tasks.add(task); + Storage.saveTasks(tasks); + + StringBuilder sb = new StringBuilder(); + sb.append(" Got it. I've added this task:\n"); + sb.append(" ").append(task).append("\n"); + sb.append(" Now you have ").append(tasks.size()).append(" tasks in the list.\n"); + return sb.toString(); + } catch (CenaInvalidDeadlineException e) { + return e.getMessage(); + } + + } +} diff --git a/src/main/java/johncena/commands/DeleteCommand.java b/src/main/java/johncena/commands/DeleteCommand.java new file mode 100644 index 0000000000..7ddbd6a1e7 --- /dev/null +++ b/src/main/java/johncena/commands/DeleteCommand.java @@ -0,0 +1,51 @@ +package johncena.commands; + +import java.util.ArrayList; + +import johncena.exceptions.CenaInvalidTaskIndexException; +import johncena.storage.Storage; +import johncena.tasks.Task; + + +/** + * The {@code DeleteCommand} class implements the {@code Command} interface and provides + * the functionality to execute the "delete" command, which removes a task from the task list. + */ +public class DeleteCommand implements Command { + private ArrayList tasks; + private int taskIndex; + + /** + * Constructs a new {@code DeleteCommand} with the specified task list and task index. + * + * @param tasks the list of tasks + * @param taskIndex the index of the task to remove + */ + public DeleteCommand(ArrayList tasks, int taskIndex) { + this.tasks = tasks; + this.taskIndex = taskIndex; + } + + + /** + * Executes the "delete" command. Removes a task from the task list. + * + * @throws CenaInvalidTaskIndexException if the task index is invalid + */ + @Override + public String execute() throws CenaInvalidTaskIndexException { + if (taskIndex < 0 || taskIndex >= tasks.size()) { + throw new CenaInvalidTaskIndexException("The task index is invalid."); + } + Task removedTask = tasks.remove(taskIndex); + Storage.saveTasks(tasks); + + StringBuilder sb = new StringBuilder(); + sb.append(" Noted. I've removed this task. Now, you can't see me and neither can you see that task:\n"); + sb.append(" ").append(removedTask).append("\n"); + sb.append(" Now you have ").append(tasks.size()).append(" tasks in the list.\n"); + + return sb.toString(); + + } +} diff --git a/src/main/java/johncena/commands/EventCommand.java b/src/main/java/johncena/commands/EventCommand.java new file mode 100644 index 0000000000..0d474cded0 --- /dev/null +++ b/src/main/java/johncena/commands/EventCommand.java @@ -0,0 +1,64 @@ +package johncena.commands; + +import java.util.ArrayList; + +import johncena.exceptions.CenaInvalidEventException; +import johncena.storage.Storage; +import johncena.tasks.Event; +import johncena.tasks.Task; + +/** + * The {@code EventCommand} class implements the {@code Command} interface and provides + * the functionality to execute the "event" command, which adds an event task to the task list. + */ +public class EventCommand implements Command { + private ArrayList tasks; + private String description; + private String from; + private String to; + + /** + * Constructs a new {@code EventCommand} with the specified task list, description, start time, and end time. + * + * @param tasks the list of tasks + * @param description the description of the event task + * @param from the start time of the event + * @param to the end time of the event + */ + public EventCommand(ArrayList tasks, String description, String from, String to) { + this.tasks = tasks; + this.description = description; + this.from = from; + this.to = to; + } + + + /** + * Executes the "event" command. Adds an event task to the task list. + * + * @throws CenaInvalidEventException if the description for the event is incorrect + */ + @Override + public String execute() { + assert description != null : "Description should not be null"; + assert from != null : "Start time should not be null"; + assert to != null : "End time should not be null"; + try { + if (description.isEmpty() || from.isEmpty() || to.isEmpty()) { + throw new CenaInvalidEventException("Incorrect description for event. It should contain /from and /to"); + } + Task task = new Event(description, from, to); + tasks.add(task); + Storage.saveTasks(tasks); + + StringBuilder sb = new StringBuilder(); + sb.append(" Got it. I've added this task:\n"); + sb.append(" ").append(task).append("\n"); + sb.append(" Now you have ").append(tasks.size()).append(" tasks in the list. Keep hustling!\n"); + return sb.toString(); + } catch (CenaInvalidEventException e) { + return e.getMessage(); + } + + } +} diff --git a/src/main/java/johncena/commands/FindCommand.java b/src/main/java/johncena/commands/FindCommand.java new file mode 100644 index 0000000000..10c828aeac --- /dev/null +++ b/src/main/java/johncena/commands/FindCommand.java @@ -0,0 +1,45 @@ +package johncena.commands; + +import java.util.ArrayList; + +import johncena.tasks.Task; + +/** + * The {@code FindCommand} class implements the {@code Command} interface and provides + * the functionality to execute the "find" command, which searches for tasks that + * contain a specific keyword in their description. + */ +public class FindCommand implements Command { + + private ArrayList tasks; + private String keyword; + + /** + * Constructs a new {@code FindCommand} with the specified task list and keyword. + * + * @param tasks the list of tasks + * @param keyword the keyword to search for + */ + public FindCommand(ArrayList tasks, String keyword) { + this.tasks = tasks; + this.keyword = keyword; + } + + /** + * Executes the "find" command. Searches for tasks that contain the keyword + * in their description and prints the matching tasks. + */ + @Override + public String execute() { + StringBuilder sb = new StringBuilder(); + sb.append(" Here are the matching tasks in your list:\n"); + int index = 1; + for (Task task : tasks) { + if (task.getDescription().contains(keyword)) { + sb.append(" ").append(index).append(".").append(task).append("\n"); + } + index++; + } + return sb.toString(); + } +} diff --git a/src/main/java/johncena/commands/HelloCommand.java b/src/main/java/johncena/commands/HelloCommand.java new file mode 100644 index 0000000000..2dd10bc853 --- /dev/null +++ b/src/main/java/johncena/commands/HelloCommand.java @@ -0,0 +1,24 @@ +package johncena.commands; + +import johncena.art.Logo; + + +/** + * Represents the "hello" command. + * A HelloCommand object corresponds to a command to greet the user. + */ +public class HelloCommand implements Command { + + /** + * Executes the "hello" command. + * + * @return The response to the user. + */ + @Override + public String execute() { + StringBuilder sb = new StringBuilder(); + sb.append("Hello from\n").append(Logo.getLogo()).append("\n"); + sb.append("You can't see me! But I'm here to help. What can I do for you today?\n"); + return sb.toString(); + } +} diff --git a/src/main/java/johncena/commands/HelpCommand.java b/src/main/java/johncena/commands/HelpCommand.java new file mode 100644 index 0000000000..e8fe09ad13 --- /dev/null +++ b/src/main/java/johncena/commands/HelpCommand.java @@ -0,0 +1,33 @@ +package johncena.commands; + +/** + * The {@code HelpCommand} class implements the {@code Command} interface and provides + * the functionality to execute the "help" command, which displays the list of commands + * that the user can use. + */ +public class HelpCommand implements Command { + + + /** + * Executes the "help" command. Displays the list of commands that the user can use. + */ + @Override + public String execute() { + StringBuilder sb = new StringBuilder(); + sb.append("Here are the commands you can use:\n"); + sb.append(" bye - Exits the program\n"); + sb.append(" list - Lists all tasks\n"); + sb.append(" mark [task number] - Marks a task as done\n"); + sb.append(" unmark [task number] - Marks a task as not done\n"); + sb.append(" delete [task number] - Deletes a task\n"); + sb.append(" todo [description] - Adds a todo task\n"); + sb.append(" deadline [description] /by [due date] - Adds a deadline task\n"); + sb.append(" event [description] /from [start date] /to [end date] - Adds an event task\n"); + sb.append(" find [keyword] - Finds tasks with a specific keyword\n"); + sb.append(" after [desription] /after [date] - Adds a task that you will do after a specific date\n"); + sb.append(" on [date] - Lists all tasks on a specific date\n"); + sb.append(" hello - Displays the welcome message\n"); + sb.append(" help - Displays the list of commands\n"); + return sb.toString(); + } +} diff --git a/src/main/java/johncena/commands/ListCommand.java b/src/main/java/johncena/commands/ListCommand.java new file mode 100644 index 0000000000..29c8af8c3a --- /dev/null +++ b/src/main/java/johncena/commands/ListCommand.java @@ -0,0 +1,37 @@ +package johncena.commands; + +import java.util.ArrayList; + +import johncena.tasks.Task; + +/** + * The {@code ListCommand} class implements the {@code Command} interface and provides + * the functionality to execute the "list" command, which lists all tasks in the task list. + */ +public class ListCommand implements Command { + private ArrayList tasks; + + /** + * Constructs a new {@code ListCommand} with the specified task list. + * + * @param tasks the list of tasks + */ + public ListCommand(ArrayList tasks) { + this.tasks = tasks; + } + + /** + * Executes the "list" command. Lists all tasks in the task list. + */ + @Override + public String execute() { + + StringBuilder sb = new StringBuilder(); + sb.append(" Alright, Champ! Here are the tasks in your list:\n"); + for (int i = 0; i < tasks.size(); i++) { + sb.append(" ").append(i + 1).append(".").append(tasks.get(i)).append("\n"); + } + sb.append("Keep pushing forward and never give up!\n"); + return sb.toString(); + } +} diff --git a/src/main/java/johncena/commands/MarkCommand.java b/src/main/java/johncena/commands/MarkCommand.java new file mode 100644 index 0000000000..ae8ffc0747 --- /dev/null +++ b/src/main/java/johncena/commands/MarkCommand.java @@ -0,0 +1,46 @@ +package johncena.commands; + +import java.util.ArrayList; + +import johncena.exceptions.CenaInvalidTaskIndexException; +import johncena.storage.Storage; +import johncena.tasks.Task; + +/** + * The {@code MarkCommand} class implements the {@code Command} interface and provides + * the functionality to execute the "mark" command, which marks a task as done in the task list. + */ +public class MarkCommand implements Command { + private ArrayList tasks; + private int taskIndex; + + /** + * Constructs a new {@code MarkCommand} with the specified task list and task index. + * + * @param tasks the list of tasks + * @param taskIndex the index of the task to be marked as done + */ + public MarkCommand(ArrayList tasks, int taskIndex) { + this.tasks = tasks; + this.taskIndex = taskIndex; + } + + /** + * Executes the "mark" command. Marks a task as done in the task list. + * + * @throws CenaInvalidTaskIndexException if the task index is invalid + */ + @Override + public String execute() throws CenaInvalidTaskIndexException { + if (taskIndex < 0 || taskIndex >= tasks.size()) { + throw new CenaInvalidTaskIndexException("The task index is invalid."); + } + tasks.get(taskIndex).markAsDone(); + Storage.saveTasks(tasks); + + StringBuilder sb = new StringBuilder(); + sb.append(" You did it, Champ! I've marked this task as done:\n"); + sb.append(" ").append(tasks.get(taskIndex)).append("\n"); + return sb.toString(); + } +} diff --git a/src/main/java/johncena/commands/OnCommand.java b/src/main/java/johncena/commands/OnCommand.java new file mode 100644 index 0000000000..6df0327939 --- /dev/null +++ b/src/main/java/johncena/commands/OnCommand.java @@ -0,0 +1,49 @@ +package johncena.commands; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; + +import johncena.tasks.Task; + +/** + * The {@code OnCommand} class implements the {@code Command} interface and provides + * the functionality to execute the "on" command, which lists all tasks on a specified date. + */ +public class OnCommand implements Command { + private ArrayList tasks; + private LocalDate targetDate; + + /** + * Constructs a new {@code OnCommand} with the specified task list and date. + * + * @param tasks the list of tasks + * @param date the date to filter tasks by + * @throws DateTimeParseException if the date string is in an incorrect format + */ + public OnCommand(ArrayList tasks, String date) throws DateTimeParseException { + this.tasks = tasks; + // Parse the date string into a LocalDate object using the specified format + this.targetDate = LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } + + /** + * Executes the "on" command. Lists all tasks on the specified date. + */ + @Override + public String execute() { + + StringBuilder sb = new StringBuilder(); + sb.append(" Alright, Champ! Here are the tasks on ") + .append(targetDate.format(DateTimeFormatter.ofPattern("MMM d yyyy"))) + .append(":\n"); + for (int i = 0; i < tasks.size(); i++) { + Task task = tasks.get(i); + if (task.occursOn(targetDate)) { + sb.append(" ").append(i + 1).append(".").append(task).append("\n"); + } + } + return sb.toString(); + } +} diff --git a/src/main/java/johncena/commands/TodoCommand.java b/src/main/java/johncena/commands/TodoCommand.java new file mode 100644 index 0000000000..3bda26c07b --- /dev/null +++ b/src/main/java/johncena/commands/TodoCommand.java @@ -0,0 +1,58 @@ +package johncena.commands; + +import java.util.ArrayList; + +import johncena.exceptions.CenaEmptyDescriptionException; +import johncena.exceptions.CenaInvalidTodoException; +import johncena.storage.Storage; +import johncena.tasks.Task; +import johncena.tasks.Todo; + + +/** + * The {@code TodoCommand} class implements the {@code Command} interface and provides + * the functionality to execute the "todo" command, which adds a todo task to the task list. + */ +public class TodoCommand implements Command { + private ArrayList tasks; + private String description; + + /** + * Constructs a new {@code TodoCommand} with the specified task list and description. + * + * @param tasks the list of tasks + * @param description the description of the todo task + */ + public TodoCommand(ArrayList tasks, String description) { + this.tasks = tasks; + this.description = description; + } + + /** + * Executes the "todo" command. Adds a todo task to the task list. + * + * @throws CenaEmptyDescriptionException if the description of the todo is empty + * @throws CenaInvalidTodoException if the description of the todo is invalid + */ + @Override + public String execute() throws CenaEmptyDescriptionException, CenaInvalidTodoException { + assert description != null : "Description should not be null"; + + if (description.isEmpty()) { + throw new CenaEmptyDescriptionException("The description of a todo cannot be empty."); + } + if (description.contains("/by") || description.contains("/from") || description.contains("/to")) { + throw new CenaInvalidTodoException("A todo should not contain /by, /from, or /to."); + } + Task task = new Todo(description); + tasks.add(task); + Storage.saveTasks(tasks); + + StringBuilder sb = new StringBuilder(); + sb.append(" Alright, Champ! I've added this task:\n"); + sb.append(" ").append(task).append("\n"); + sb.append(" Now you have ").append(tasks.size()).append(" tasks in the list. Keep hustling!\n"); + return sb.toString(); + } +} + diff --git a/src/main/java/johncena/commands/UnmarkCommand.java b/src/main/java/johncena/commands/UnmarkCommand.java new file mode 100644 index 0000000000..030cadce5e --- /dev/null +++ b/src/main/java/johncena/commands/UnmarkCommand.java @@ -0,0 +1,49 @@ +package johncena.commands; + +import java.util.ArrayList; + +import johncena.exceptions.CenaInvalidTaskIndexException; +import johncena.storage.Storage; +import johncena.tasks.Task; + +/** + * The {@code UnmarkCommand} class implements the {@code Command} interface and provides + * the functionality to execute the "unmark" command, which marks a task as not done in the task list. + */ +public class UnmarkCommand implements Command { + private ArrayList tasks; + private int taskIndex; + + /** + * Constructs a new {@code UnmarkCommand} with the specified task list and task index. + * + * @param tasks the list of tasks + * @param taskIndex the index of the task to be marked as not done + */ + public UnmarkCommand(ArrayList tasks, int taskIndex) { + this.tasks = tasks; + this.taskIndex = taskIndex; + } + + /** + * Executes the "unmark" command. Marks a task as not done in the task list. + * + * @throws CenaInvalidTaskIndexException if the task index is invalid + */ + @Override + public String execute() throws CenaInvalidTaskIndexException { + assert taskIndex >= 0 : "Task index should be non-negative"; + assert taskIndex < tasks.size() : "Task index should be within the bounds of the task list"; + if (taskIndex < 0 || taskIndex >= tasks.size()) { + throw new CenaInvalidTaskIndexException("The task index is invalid."); + } + tasks.get(taskIndex).markAsNotDone(); + Storage.saveTasks(tasks); + + StringBuilder sb = new StringBuilder(); + sb.append(" Alright , Champ! I've marked this task as not done yet:\n"); + sb.append(" ").append(tasks.get(taskIndex)).append("\n"); + return sb.toString(); + } + +} diff --git a/src/main/java/johncena/exceptions/CenaEmptyDescriptionException.java b/src/main/java/johncena/exceptions/CenaEmptyDescriptionException.java new file mode 100644 index 0000000000..625316aab5 --- /dev/null +++ b/src/main/java/johncena/exceptions/CenaEmptyDescriptionException.java @@ -0,0 +1,17 @@ +package johncena.exceptions; + +/** + * This class represents an exception that is thrown when an empty description is encountered. + */ +public class CenaEmptyDescriptionException extends CenaException { + + /** + * Constructs a new exceptions.CenaEmptyDescriptionException with the specified detail message. + * + * @param message the detail message. The detail message is saved for later retrieval by the Throwable.getMessage() + * method. + */ + public CenaEmptyDescriptionException(String message) { + super(message); + } +} diff --git a/src/main/java/johncena/exceptions/CenaException.java b/src/main/java/johncena/exceptions/CenaException.java new file mode 100644 index 0000000000..5ecb5030af --- /dev/null +++ b/src/main/java/johncena/exceptions/CenaException.java @@ -0,0 +1,17 @@ +package johncena.exceptions; + +/** + * This class represents a general exception for the Cena application. + */ +public class CenaException extends Exception { + + /** + * Constructs a new exceptions.CenaException with the specified detail message. + * + * @param message the detail message. The detail message is saved for later retrieval by the Throwable.getMessage() + * method. + */ + public CenaException(String message) { + super(message); + } +} diff --git a/src/main/java/johncena/exceptions/CenaInvalidAfterException.java b/src/main/java/johncena/exceptions/CenaInvalidAfterException.java new file mode 100644 index 0000000000..5ffa2026d2 --- /dev/null +++ b/src/main/java/johncena/exceptions/CenaInvalidAfterException.java @@ -0,0 +1,15 @@ +package johncena.exceptions; + +/** + * Represents an exception thrown when an invalid format is used for an after task. + */ +public class CenaInvalidAfterException extends CenaException { + /** + * Constructor for CenaInvalidAfterException. + * + * @param message The error message. + */ + public CenaInvalidAfterException(String message) { + super(message); + } +} diff --git a/src/main/java/johncena/exceptions/CenaInvalidDateTimeException.java b/src/main/java/johncena/exceptions/CenaInvalidDateTimeException.java new file mode 100644 index 0000000000..e19020f7d7 --- /dev/null +++ b/src/main/java/johncena/exceptions/CenaInvalidDateTimeException.java @@ -0,0 +1,30 @@ +package johncena.exceptions; + +import java.time.format.DateTimeParseException; + +/** + * This class represents an exception that is thrown when an invalid date time is encountered. + */ +public class CenaInvalidDateTimeException extends Exception { + + /** + * Constructs a new exceptions.CenaInvalidDateTimeException with the specified detail message. + * + * @param message the detail message. The detail message is saved for later retrieval by the Throwable.getMessage() + * method. + */ + public CenaInvalidDateTimeException(String message) { + super(message); + } + + /** + * Constructs a new exceptions.CenaInvalidDateTimeException with the specified detail message and cause. + * + * @param message the detail message. The detail message is saved for later retrieval by the Throwable.getMessage() + * method. + * @param cause the cause of the exception (which is saved for later retrieval by the Throwable.getCause() method). + */ + public CenaInvalidDateTimeException(String message, DateTimeParseException cause) { + super(message, cause); + } +} diff --git a/src/main/java/johncena/exceptions/CenaInvalidDeadlineException.java b/src/main/java/johncena/exceptions/CenaInvalidDeadlineException.java new file mode 100644 index 0000000000..c84f0edb37 --- /dev/null +++ b/src/main/java/johncena/exceptions/CenaInvalidDeadlineException.java @@ -0,0 +1,17 @@ +package johncena.exceptions; + +/** + * This class represents an exception that is thrown when an invalid deadline is encountered. + */ +public class CenaInvalidDeadlineException extends CenaException { + + /** + * Constructs a new exceptions.CenaInvalidDeadlineException with the specified detail message. + * + * @param message the detail message. The detail message is saved for later retrieval by the Throwable.getMessage() + * method. + */ + public CenaInvalidDeadlineException(String message) { + super(message); + } +} diff --git a/src/main/java/johncena/exceptions/CenaInvalidEventException.java b/src/main/java/johncena/exceptions/CenaInvalidEventException.java new file mode 100644 index 0000000000..86d1b9aea8 --- /dev/null +++ b/src/main/java/johncena/exceptions/CenaInvalidEventException.java @@ -0,0 +1,17 @@ +package johncena.exceptions; + +/** + * This class represents an exception that is thrown when an invalid event is encountered. + */ +public class CenaInvalidEventException extends CenaException { + + /** + * Constructs a new exceptions.CenaInvalidEventException with the specified detail message. + * + * @param message the detail message. The detail message is saved for later retrieval by the Throwable.getMessage() + * method. + */ + public CenaInvalidEventException(String message) { + super(message); + } +} diff --git a/src/main/java/johncena/exceptions/CenaInvalidTaskIndexException.java b/src/main/java/johncena/exceptions/CenaInvalidTaskIndexException.java new file mode 100644 index 0000000000..bb563da3a9 --- /dev/null +++ b/src/main/java/johncena/exceptions/CenaInvalidTaskIndexException.java @@ -0,0 +1,17 @@ +package johncena.exceptions; + +/** + * This class represents an exception that is thrown when an invalid task index is encountered. + */ +public class CenaInvalidTaskIndexException extends CenaException { + + /** + * Constructs a new exceptions.CenaInvalidTaskIndexException with the specified detail message. + * + * @param message the detail message. The detail message is saved for later retrieval by the Throwable.getMessage() + * method. + */ + public CenaInvalidTaskIndexException(String message) { + super(message); + } +} diff --git a/src/main/java/johncena/exceptions/CenaInvalidTodoException.java b/src/main/java/johncena/exceptions/CenaInvalidTodoException.java new file mode 100644 index 0000000000..229f003102 --- /dev/null +++ b/src/main/java/johncena/exceptions/CenaInvalidTodoException.java @@ -0,0 +1,17 @@ +package johncena.exceptions; + +/** + * This class represents an exception that is thrown when an invalid todo is encountered. + */ +public class CenaInvalidTodoException extends CenaException { + + /** + * Constructs a new exceptions.CenaInvalidTodoException with the specified detail message. + * + * @param message the detail message. The detail message is saved for later retrieval by the Throwable.getMessage() + * method. + */ + public CenaInvalidTodoException(String message) { + super(message); + } +} diff --git a/src/main/java/johncena/exceptions/CenaUnknownCommandException.java b/src/main/java/johncena/exceptions/CenaUnknownCommandException.java new file mode 100644 index 0000000000..51f6e05be7 --- /dev/null +++ b/src/main/java/johncena/exceptions/CenaUnknownCommandException.java @@ -0,0 +1,17 @@ +package johncena.exceptions; + +/** + * This class represents an exception that is thrown when an unknown command is encountered. + */ +public class CenaUnknownCommandException extends CenaException { + + /** + * Constructs a new exceptions.CenaUnknownCommandException with the specified detail message. + * + * @param message the detail message. The detail message is saved for later retrieval by the Throwable.getMessage() + * method. + */ + public CenaUnknownCommandException(String message) { + super(message); + } +} diff --git a/src/main/java/johncena/gui/DialogBox.java b/src/main/java/johncena/gui/DialogBox.java new file mode 100644 index 0000000000..3b10b73c24 --- /dev/null +++ b/src/main/java/johncena/gui/DialogBox.java @@ -0,0 +1,61 @@ +package johncena.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; + +/** + * 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); + dialog.getStyleClass().add("reply-label"); + } + + public static DialogBox getUserDialog(String text, Image img) { + return new DialogBox(text, img); + } + + public static DialogBox getJohnCenaDialog(String text, Image img) { + var db = new DialogBox(text, img); + db.flip(); + return db; + } +} diff --git a/src/main/java/johncena/gui/Main.java b/src/main/java/johncena/gui/Main.java new file mode 100644 index 0000000000..d2c8b977d1 --- /dev/null +++ b/src/main/java/johncena/gui/Main.java @@ -0,0 +1,35 @@ +package johncena.gui; + +import java.io.IOException; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; +import johncena.JohnCena; + +/** + * A GUI for Duke using FXML. + */ +public class Main extends Application { + + private JohnCena johnCena = new JohnCena(); + + @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.setMinHeight(220); + stage.setMinWidth(417); + fxmlLoader.getController().setJohnCena(johnCena); // inject the JohnCena instance + stage.setTitle("John Cena Task Manager"); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/johncena/gui/MainWindow.java b/src/main/java/johncena/gui/MainWindow.java new file mode 100644 index 0000000000..6da4dce038 --- /dev/null +++ b/src/main/java/johncena/gui/MainWindow.java @@ -0,0 +1,54 @@ +package johncena.gui; + +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import johncena.JohnCena; + +/** + * Controller for the main GUI. + */ +public class MainWindow extends AnchorPane { + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogContainer; + @FXML + private TextField userInput; + @FXML + private Button sendButton; + + private JohnCena johnCena; + + private Image userImage = new Image(this.getClass().getResourceAsStream("/images/nerdglasses.png")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/johncena.png")); + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + } + + /** Injects the JohnCena instance */ + public void setJohnCena(JohnCena johncena) { + johnCena = johncena; + } + + /** + * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to + * the dialog container. Clears the user input after processing. + */ + @FXML + private void handleUserInput() { + String input = userInput.getText(); + String response = johnCena.getResponse(input); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(input, userImage), + DialogBox.getJohnCenaDialog(response, dukeImage) + ); + userInput.clear(); + } +} diff --git a/src/main/java/johncena/parser/InputHandler.java b/src/main/java/johncena/parser/InputHandler.java new file mode 100644 index 0000000000..3a10ca3548 --- /dev/null +++ b/src/main/java/johncena/parser/InputHandler.java @@ -0,0 +1,150 @@ +package johncena.parser; + +import java.util.ArrayList; + +import johncena.commands.AfterCommand; +import johncena.commands.ByeCommand; +import johncena.commands.Command; +import johncena.commands.DeadlineCommand; +import johncena.commands.DeleteCommand; +import johncena.commands.EventCommand; +import johncena.commands.FindCommand; +import johncena.commands.HelloCommand; +import johncena.commands.HelpCommand; +import johncena.commands.ListCommand; +import johncena.commands.MarkCommand; +import johncena.commands.OnCommand; +import johncena.commands.TodoCommand; +import johncena.commands.UnmarkCommand; +import johncena.exceptions.CenaException; +import johncena.exceptions.CenaInvalidAfterException; +import johncena.exceptions.CenaInvalidDeadlineException; +import johncena.exceptions.CenaInvalidEventException; +import johncena.exceptions.CenaInvalidTaskIndexException; +import johncena.exceptions.CenaUnknownCommandException; +import johncena.tasks.Task; + +/** + * The {@code InputHandler} class handles the user input and returns the corresponding command. + */ +public class InputHandler { + private ArrayList tasks; + + /** + * Constructs a new {@code InputHandler} with the specified list of tasks. + * + * @param tasks The list of tasks. + */ + public InputHandler(ArrayList tasks) { + assert tasks != null : "Tasks list should not be null"; + this.tasks = tasks; + } + + /** + * Handles the user input and returns the corresponding command. + * + * @param input The user input. + * @return The command corresponding to the user input. + * @throws CenaException If the user input is invalid. + */ + public Command handleInput(String input) throws CenaException { + assert input != null : "Input should not be null"; + try { + if (input.equals("bye")) { + return new ByeCommand(); + } else if (input.equals("list")) { + return new ListCommand(tasks); + } else if (input.startsWith("mark ")) { + return createMarkCommand(input); + } else if (input.startsWith("unmark ")) { + return createUnmarkCommand(input); + } else if (input.startsWith("todo ")) { + return createTodoCommand(input); + } else if (input.startsWith("deadline ")) { + return createDeadlineCommand(input); + } else if (input.startsWith("event ")) { + return createEventCommand(input); + } else if (input.startsWith("after ")) { + return createAfterCommand(input); + } else if (input.startsWith("delete ")) { + return createDeleteCommand(input); + } else if (isGreeting(input)) { + return new HelloCommand(); + } else if (isHelpCommand(input)) { + return new HelpCommand(); + } else if (input.startsWith("on ")) { + return createOnCommand(input); + } else if (input.startsWith("find ")) { + return createFindCommand(input); + } else { + throw new CenaUnknownCommandException("I'm sorry, but I don't know what that means :-("); + } + } catch (NumberFormatException e) { + throw new CenaInvalidTaskIndexException("Task number must be a valid integer."); + } + } + + private Command createMarkCommand(String input) throws CenaInvalidTaskIndexException { + int taskIndex = Integer.parseInt(input.split(" ")[1]) - 1; + return new MarkCommand(tasks, taskIndex); + } + + private Command createUnmarkCommand(String input) throws CenaInvalidTaskIndexException { + int taskIndex = Integer.parseInt(input.split(" ")[1]) - 1; + return new UnmarkCommand(tasks, taskIndex); + } + + private Command createTodoCommand(String input) { + String description = input.substring(5).trim(); + return new TodoCommand(tasks, description); + } + + private Command createDeadlineCommand(String input) throws CenaInvalidDeadlineException { + String[] parts = input.substring(9).split(" /by "); + if (parts.length < 2) { + throw new CenaInvalidDeadlineException("Incorrect description for deadline. It should contain only /by."); + } + return new DeadlineCommand(tasks, parts[0], parts[1]); + } + + private Command createEventCommand(String input) throws CenaInvalidEventException { + String[] parts = input.substring(6).split(" /from | /to "); + if (parts.length < 3) { + throw new CenaInvalidEventException("Incorrect description for event. It should contain /from and /to."); + } + return new EventCommand(tasks, parts[0], parts[1], parts[2]); + } + + private Command createDeleteCommand(String input) throws CenaInvalidTaskIndexException { + int taskIndex = Integer.parseInt(input.split(" ")[1]) - 1; + return new DeleteCommand(tasks, taskIndex); + } + + private Command createOnCommand(String input) { + String date = input.substring(3).trim(); + return new OnCommand(tasks, date); + } + + private Command createFindCommand(String input) { + String keyword = input.substring(5).trim(); + return new FindCommand(tasks, keyword); + } + + private Command createAfterCommand(String input) throws CenaInvalidAfterException { + String[] parts = input.substring(6).split(" /after "); + if (parts.length < 2) { + throw new CenaInvalidAfterException("Incorrect description for after task. It should contain /after."); + } + return new AfterCommand(tasks, parts[0], parts[1]); + } + + private boolean isGreeting(String input) { + return input.equals("hello") || input.equals("hi") || input.equals("hey") || input.equals("yo") + || input.equals("sup"); + } + + private boolean isHelpCommand(String input) { + return input.equals("help") || input.equals("commands") || input.equals("command") || input.equals("cmds") + || input.equals("cmd"); + } +} diff --git a/src/main/java/johncena/storage/Storage.java b/src/main/java/johncena/storage/Storage.java new file mode 100644 index 0000000000..86bad5d365 --- /dev/null +++ b/src/main/java/johncena/storage/Storage.java @@ -0,0 +1,139 @@ +package johncena.storage; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; + +import johncena.exceptions.CenaException; +import johncena.tasks.After; +import johncena.tasks.Deadline; +import johncena.tasks.Event; +import johncena.tasks.Task; +import johncena.tasks.Todo; + + + +/** + * The {@code Storage} class provides the functionality to save and load tasks from a file. + ** This class was inspired by the work of jasmiinee, particularly in the methods for saving and loading tasks. + */ +public class Storage { + public static final String SEPARATOR = " | "; + private static String filePath = "./data/CenaTaskList.txt"; // Default file path + + public static void setFilePath(String path) { + assert path != null : "File path should not be null"; + filePath = path; + } + + /** + * Saves the list of tasks to the storage file. + * + * @param tasks The list of tasks to be saved. + */ + public static void saveTasks(ArrayList tasks) { + assert tasks != null : "Tasks list should not be null"; + try (FileWriter fw = new FileWriter(filePath)) { + for (Task task : tasks) { + fw.write(toSaveString(task) + System.lineSeparator()); + } + } catch (IOException e) { + System.out.println("Error: " + e.getMessage()); + } + } + + /** + * Loads the list of tasks from the storage file. + * + * @return An ArrayList of tasks loaded from the file. + */ + public static ArrayList loadTasks() { + ArrayList tasks = new ArrayList<>(); + try { + File file = new File(filePath); + createFileIfNotExists(file); + readTasksFromFile(tasks, file); + } catch (IOException e) { + System.out.println("Error: " + e.getMessage()); + } + return tasks; + } + + private static void createFileIfNotExists(File file) throws IOException { + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + if (!file.exists()) { + file.createNewFile(); + } + } + + private static void readTasksFromFile(ArrayList tasks, File file) throws IOException { + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + try { + tasks.add(parseTask(line)); + } catch (CenaException e) { + System.out.println(e.getMessage()); + } + } + } + } + + private static Task parseTask(String line) throws CenaException { + assert line != null : "Line to parse should not be null"; + Task task = null; + String[] taskDescription = line.trim().split("\\s*\\|\\s*"); + String taskType = taskDescription[0]; + + switch (taskType) { + case "T": + task = new Todo(taskDescription[2]); + break; + case "D": + task = new Deadline(taskDescription[2], taskDescription[3]); + break; + case "E": + String[] eventTimes = taskDescription[3].split(" ~ "); + task = new Event(taskDescription[2], eventTimes[0], eventTimes[1]); + break; + case "A": + task = new After(taskDescription[2], taskDescription[3]); + break; + default: + System.out.println("Unknown task type: " + taskType); + break; + } + + if (task != null && taskDescription[1].equals("1")) { + task.markAsDone(); + } + + return task; + } + + private static String toSaveString(Task task) { + assert task != null : "Task to save should not be null"; + String taskStatus = task.isTaskDone() ? "1" : "0"; + String taskDescription = ""; + + if (task instanceof Todo todo) { + taskDescription = "T" + SEPARATOR + taskStatus + SEPARATOR + todo.getDescription(); + } else if (task instanceof Deadline deadline) { + taskDescription = "D" + SEPARATOR + taskStatus + SEPARATOR + deadline.getDescription() + SEPARATOR + + deadline.getBy(); + } else if (task instanceof Event event) { + taskDescription = "E" + SEPARATOR + taskStatus + SEPARATOR + event.getDescription() + SEPARATOR + + event.getFrom() + " ~ " + event.getTo(); + } else if (task instanceof After after) { + taskDescription = "A" + SEPARATOR + taskStatus + SEPARATOR + after.getDescription() + SEPARATOR + + after.getAfter(); + } + + return taskDescription; + } +} diff --git a/src/main/java/johncena/tasks/After.java b/src/main/java/johncena/tasks/After.java new file mode 100644 index 0000000000..32cbafb468 --- /dev/null +++ b/src/main/java/johncena/tasks/After.java @@ -0,0 +1,53 @@ +package johncena.tasks; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +import johncena.exceptions.CenaInvalidAfterException; + +/** + * Represents an after task. + * An After object corresponds to a task represented by a description and a time after which it should be done. + */ +public class After extends Task { + protected LocalDateTime after; + + /** + * Constructor for After class. + * + * @param description The description of the task. + * @param after The time after which the task should be done. + * The after should be in the format yyyy-MM-dd HHmm. + * @throws CenaInvalidAfterException if the after format is incorrect + */ + public After(String description, String after) throws CenaInvalidAfterException { + super(description); + assert description != null : "Description should not be null"; + assert after != null : "After time should not be null"; + try { + this.after = LocalDateTime.parse(after, DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm")); + } catch (DateTimeParseException e) { + throw new CenaInvalidAfterException("Incorrect format for after time. Please use yyyy-MM-dd HHmm."); + } + } + + /** + * Returns the time after which the task should be done. + * + * @return After time of the task. + */ + public String getAfter() { + return this.after.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm")); + } + + /** + * Returns a string representation of the task. + * + * @return String representation of the task. + */ + @Override + public String toString() { + return "[A]" + super.toString() + " (after: " + this.getAfter() + ")"; + } +} diff --git a/src/main/java/johncena/tasks/Deadline.java b/src/main/java/johncena/tasks/Deadline.java new file mode 100644 index 0000000000..c8488e2368 --- /dev/null +++ b/src/main/java/johncena/tasks/Deadline.java @@ -0,0 +1,66 @@ +package johncena.tasks; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +import johncena.exceptions.CenaInvalidDeadlineException; + +/** + * Represents a deadline task. + * A tasks.Deadline object corresponds to a task represented by a description and a deadline date. + */ +public class Deadline extends Task { + protected LocalDateTime by; + + /** + * Constructor for tasks.Deadline class. + * + * @param description The description of the task. + * @param by The deadline of the task. + * The deadline should be in the format yyyy-MM-dd HHmm. + * @throws CenaInvalidDeadlineException if the deadline format is incorrect + */ + public Deadline(String description, String by) throws CenaInvalidDeadlineException { + super(description); + assert description != null : "Description should not be null"; + assert by != null : "Deadline should not be null"; + try { + this.by = LocalDateTime.parse(by, DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm")); + } catch (DateTimeParseException e) { + throw new CenaInvalidDeadlineException("Incorrect format for deadline. Please use yyyy-MM-dd HHmm."); + } + } + + /** + * Returns the deadline of the task. + * + * @return tasks.Deadline of the task. + */ + public String getBy() { + + return this.by.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm")); + } + + /** + * Checks if the task occurs on the given date. + * + * @param date The date to check. + * @return True if the task occurs on the given date, false otherwise. + */ + @Override + public boolean occursOn(LocalDate date) { + return this.by.toLocalDate().equals(date); + } + + /** + * Returns a string representation of the task. + * + * @return String representation of the task. + */ + @Override + public String toString() { + return "[D]" + super.toString() + " (by: " + this.getBy() + ")"; + } +} diff --git a/src/main/java/johncena/tasks/Event.java b/src/main/java/johncena/tasks/Event.java new file mode 100644 index 0000000000..b33d854b38 --- /dev/null +++ b/src/main/java/johncena/tasks/Event.java @@ -0,0 +1,76 @@ +package johncena.tasks; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +import johncena.exceptions.CenaInvalidEventException; + +/** + * Represents an event task. + * A tasks.Event object corresponds to a task represented by a description, a start time, and an end time. + */ +public class Event extends Task { + protected LocalDateTime from; + protected LocalDateTime to; + + /** + * Constructor for tasks.Event class. + * + * @param description The description of the task. + * @param from The start time of the event. + * @param to The end time of the event. + */ + public Event(String description, String from, String to) throws CenaInvalidEventException { + super(description); + assert description != null : "Description should not be null"; + assert from != null : "Event start date should not be null"; + assert to != null : "Event end date should not be null"; + try { + this.from = LocalDateTime.parse(from, DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm")); + this.to = LocalDateTime.parse(to, DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm")); + } catch (DateTimeParseException e) { + throw new CenaInvalidEventException("Incorrect format for event dates. Please use yyyy-MM-dd HHmm."); + } + } + + /** + * Returns the start time of the event. + * + * @return Start time of the event. + */ + public String getFrom() { + return this.from.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm")); + } + + /** + * Returns the end time of the event. + * + * @return End time of the event. + */ + public String getTo() { + return this.to.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm")); + } + + /** + * Checks if the task occurs on the given date. + * + * @param date The date to check. + * @return True if the task occurs on the given date, false otherwise. + */ + @Override + public boolean occursOn(LocalDate date) { + return this.from.toLocalDate().equals(date) || this.to.toLocalDate().equals(date); + } + + /** + * Returns a string representation of the task. + * + * @return String representation of the task. + */ + @Override + public String toString() { + return "[E]" + super.toString() + " (from: " + getFrom() + " to: " + getTo() + ")"; + } +} diff --git a/src/main/java/johncena/tasks/Task.java b/src/main/java/johncena/tasks/Task.java new file mode 100644 index 0000000000..6c7d177737 --- /dev/null +++ b/src/main/java/johncena/tasks/Task.java @@ -0,0 +1,84 @@ +package johncena.tasks; + +import java.time.LocalDate; + +/** + * Represents a task. + * A tasks.Task object corresponds to a task represented by a description and a completion status. + */ +public class Task { + private String description; + private boolean isDone; + + /** + * Constructor for tasks.Task class. + * + * @param description The description of the task. + */ + public Task(String description) { + assert description != null : "Description should not be null"; + this.description = description; + this.isDone = false; + } + + /** + * Marks the task as done. + */ + public void markAsDone() { + this.isDone = true; + } + + /** + * Marks the task as not done. + */ + public void markAsNotDone() { + this.isDone = false; + } + + /** + * Returns the status icon of the task. + * + * @return Status icon of the task. + */ + public String getStatusIcon() { + return (isDone ? "X" : " "); // mark done task with X + } + + /** + * Returns the description of the task. + * + * @return Description of the task. + */ + public String getDescription() { + return this.description; + } + + /** + * Checks if the task is done. + * + * @return Completion status of the task. + */ + public boolean isTaskDone() { + return this.isDone; + } + + /** + * Checks if the task occurs on the given date. + * + * @param date The date to check. + * @return True if the task occurs on the given date, false otherwise. + */ + public boolean occursOn(LocalDate date) { + return false; + } + + /** + * Returns a string representation of the task. + * + * @return String representation of the task. + */ + @Override + public String toString() { + return "[" + this.getStatusIcon() + "] " + this.description; + } +} diff --git a/src/main/java/johncena/tasks/Todo.java b/src/main/java/johncena/tasks/Todo.java new file mode 100644 index 0000000000..c375d640c3 --- /dev/null +++ b/src/main/java/johncena/tasks/Todo.java @@ -0,0 +1,39 @@ +package johncena.tasks; + +import java.time.LocalDate; +/** + * Represents a todo task. + * A tasks.Todo object corresponds to a task represented by a description. + */ +public class Todo extends Task { + + /** + * Constructor for tasks.Todo class. + * + * @param description The description of the task. + */ + public Todo(String description) { + super(description); + assert description != null : "Description should not be null"; + } + + /** + * Checks if the task occurs on the given date. + * + * @param date The date to check. + * @return True if the task occurs on the given date, false otherwise. + */ + @Override + public boolean occursOn(LocalDate date) { + return false; + } + /** + * Returns a string representation of the task. + * + * @return String representation of the task. + */ + @Override + public String toString() { + return "[T]" + super.toString(); + } +} diff --git a/src/main/resources/css/dialog-box.css b/src/main/resources/css/dialog-box.css new file mode 100644 index 0000000000..7681083efe --- /dev/null +++ b/src/main/resources/css/dialog-box.css @@ -0,0 +1,25 @@ +.label { + -fx-background-color: linear-gradient(to bottom right, #00ffbf, #00ddff); + -fx-border-color: #d55e00 #009e73 #cc79a7 #0072b2; + -fx-border-width: 2px; + -fx-background-radius: 1em 1em 0 1em; + -fx-border-radius: 1em 1em 0 1em; +} + +.reply-label { + -fx-background-radius: 1em 1em 1em 0; + -fx-border-radius: 1em 1em 1em 0; +} + +#displayPicture { + /* Shadow effect on image. */ + -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.2), 10, 0.5, 5, 5); + + /* Change size of image. */ + -fx-scale-x: 1; + -fx-scale-y: 1; + + /* Rotate image clockwise by degrees. */ + -fx-rotate: 0; +} + diff --git a/src/main/resources/css/main.css b/src/main/resources/css/main.css new file mode 100644 index 0000000000..dd1145f7e7 --- /dev/null +++ b/src/main/resources/css/main.css @@ -0,0 +1,47 @@ +.root { + main-color: rgb(237, 255, 242); /* Create a looked-up color called "main-color" within root. */ + -fx-background-color: main-color; +} + +.text-field { + -fx-background-color: #d9ffe2; + -fx-font: 20px "Arial"; +} + +.button { + -fx-background-color: mediumspringgreen; + -fx-font: italic bold 16px "Arial"; +} + +.button:hover { + -fx-background-color:cyan; + -fx-font-size: 18px; +} + +.button:pressed { + -fx-background-color:orange; + -fx-font-size: 20px; +} + +.scroll-pane, +.scroll-pane .viewport { + -fx-background-color: transparent; +} + +.scroll-bar { + -fx-font-size: 10px; /* Change width of scroll bar. */ + -fx-background-color: main-color; +} + +.scroll-bar .thumb { + -fx-background-color: #ff9cb4; + -fx-background-radius: 1em; +} + +/* Hides the increment and decrement buttons. */ +.scroll-bar .increment-button, +.scroll-bar .decrement-button { + -fx-pref-height: 0; + -fx-opacity: 0; +} + diff --git a/src/main/resources/images/johncena.png b/src/main/resources/images/johncena.png new file mode 100644 index 0000000000..0d163ed455 Binary files /dev/null and b/src/main/resources/images/johncena.png differ diff --git a/src/main/resources/images/nerdglasses.png b/src/main/resources/images/nerdglasses.png new file mode 100644 index 0000000000..fb3f19e951 Binary files /dev/null and b/src/main/resources/images/nerdglasses.png differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..d9aa8bbbeb --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..2a580cba7b --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,16 @@ + + + + + + + + +