diff --git a/README.md b/README.md index af0309a9e..79baab33c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Duke project template +# Rick 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. +This is a project template for a greenfield Java project. Given below are instructions on how to use it. ## Setting up in Intellij @@ -13,14 +13,16 @@ Prerequisites: JDK 17, update Intellij to the most recent version. 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. -1. 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: +1. After that, locate the `src/main/java/Rick.java` file, right-click it, and choose `Run Rick.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 - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| + ____ _ _ + | _ \(_) ___| | __ + | |_) | |/ __| |/ / + | _ <| | (__| < + |_| \_\_|\___|_|\_\ + Hello, I'm Rick (´。• ᵕ •。`) + What can I do for you? ``` **Warning:** Keep the `src\main\java` folder as the root folder for Java files (i.e., don't rename those folders or move Java files to another folder outside of this folder path), as this is the default location some tools (e.g., Gradle) expect to find Java files. diff --git a/data/Rick.txt b/data/Rick.txt new file mode 100644 index 000000000..c6b7c2364 --- /dev/null +++ b/data/Rick.txt @@ -0,0 +1,7 @@ +T | 0 | math +T | 0 | science hw +D | 0 | null | friday +E | 0 | null | 10:00 | 11:00 +D | 0 | null | today +D | 0 | apple | today +E | 0 | hw | today | tmr diff --git a/docs/README.md b/docs/README.md index 47b9f984f..226e74435 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,30 +1,98 @@ -# Duke User Guide +# Rick User Guide -// Update the title above to match the actual product name +image -// Product screenshot goes here +Rick is an expressive and friendly chatbot that helps you manage your tasks. With Rick, you can add tasks, set deadlines, schedule events, and more. This guide explains how to use all of Rick’s features. + +## How to run Rick: +1. Copy the jar file into an empty folder (you can find Rick.jar in the github release mechanism) +2. Open a command window in that folder. +3. Run the command java -jar Rick.jar (i.e., run the command in the same folder as the jar file). + +After starting, Rick will display a welcome message. Yay! you can start interacting with Rick by typing in your command and press enter. + +# Available Commands + +## 1. Existing Rick + +command: bye + +what it does: closes Rick. + +image + +## 2. Listing All Tasks + +command: list + +what it does: Shows all the tasks you have added. + +image + +## 3. Finding Task + +command: find + +what it does: search for task that contains a specific word. + +image + +## 4. Marking Task as Done + +command: mark + +what it does: mark the task with "X" + +image + +## 5. Marking Task as Undone + +command: unmark + +what it does: remove the "X" mark on task + +image + +## 6. Deleting a Task + +command: delete + +what it does: remove a task from your list + +image + +## 7. Adding a Todo Task + +command: todo + +what it does: Adds a task + +image + +## 8. Adding a Deadline Task + +command: deadline /by + +what it does: Adds a task with deadline + +image + +## 9. Adding an Event task + +command: event /from /to + +what it does: Adds a task with start time and end time + +image -// Product intro goes here -## Adding deadlines -// Describe the action and its outcome. -// Give examples of usage -Example: `keyword (optional arguments)` -// A description of the expected outcome goes here -``` -expected output -``` -## Feature ABC -// Feature details -## Feature XYZ -// Feature details \ No newline at end of file diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java new file mode 100644 index 000000000..6c07eb7f4 --- /dev/null +++ b/src/main/java/Deadline.java @@ -0,0 +1,17 @@ +public class Deadline extends Task{ + + protected String date; + public Deadline(String description, String date){ + super(description); + this.date = date; + } + @Override + public String toString(){ + return "[D]" + super.toString() + " (by: " + date + ")"; + } + @Override + public String toDataString() { + // Format: D | 1/0 | description | date + return "D | " + (isDone ? "1" : "0") + " | " + this.description + " | " + this.date; + } +} diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334c..000000000 --- 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/Event.java b/src/main/java/Event.java new file mode 100644 index 000000000..2bd4a4df3 --- /dev/null +++ b/src/main/java/Event.java @@ -0,0 +1,19 @@ +public class Event extends Task{ + + protected String startTime; + protected String endTime; + public Event(String description, String startTime, String endTime){ + super(description); + this.startTime = startTime; + this.endTime = endTime; + } + @Override + public String toString(){ + return "[E]" + super.toString() + " (from: " + startTime + " to: " + endTime + ")"; + } + @Override + public String toDataString() { + // Format: E | 1/0 | description | startTime| endTime + return "E | " + (isDone ? "1" : "0") + " | " + this.description + " | " + this.startTime + " | " + this.endTime; + } +} diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..33ef12644 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: Rick + diff --git a/src/main/java/Parser.java b/src/main/java/Parser.java new file mode 100644 index 000000000..ff6a2cbb7 --- /dev/null +++ b/src/main/java/Parser.java @@ -0,0 +1,112 @@ +import java.util.ArrayList; + +/** + *Identify key command words such as "bye", "list", "find", "mark", "unmark", "delete", "todo", "deadline" and "event". + */ +public class Parser { + + /** + * + * @param input + * @param taskList + * @param storage + * @param ui + * @return true to exit program. If return false, while loop in Rick class will keep running parse method. + */ + public static boolean parse(String input, TaskList taskList, Storage storage, Ui ui) { + String trimmedInput = input.trim(); + + if (trimmedInput.equals("bye")) { + ui.showGoodbye(); + return true; // signal to exit the program + } else if (trimmedInput.equals("list")) { + ui.showTaskList(taskList.getTasks()); + } + else if (trimmedInput.startsWith("find")) { // NEW: find command + String keyword = trimmedInput.substring(4).trim(); + if (keyword.isEmpty()) { + ui.showError("The search keyword cannot be empty. (✖╭╮✖)\n"); + } else { + // Use TaskList's findTasks method to search for tasks containing the keyword + ArrayList foundTasks = taskList.findTasks(keyword); + ui.showFoundTasks(foundTasks); + } + } + else if (trimmedInput.startsWith("mark")) { + try { + String noSpace = trimmedInput.replaceAll("\\s",""); + int index = Integer.parseInt(noSpace.substring(4)) - 1; + Task task = taskList.getTask(index); + task.markAsDone(); + ui.showTaskDone(task); + storage.saveTasks(taskList.getTasks()); + } catch (NumberFormatException | IndexOutOfBoundsException e) { + ui.showError("Invalid task number for marking! (✖ _ ✖)\n"); + } + } else if (trimmedInput.startsWith("unmark")) { + try { + String noSpace = trimmedInput.replaceAll("\\s",""); + int index = Integer.parseInt(noSpace.substring(6)) - 1; + Task task = taskList.getTask(index); + task.markAsUndone(); + ui.showTaskUndone(task); + storage.saveTasks(taskList.getTasks()); + } catch (NumberFormatException | IndexOutOfBoundsException e) { + ui.showError("Invalid task number for unmarking! (´・_・`)"); + } + } else if (trimmedInput.startsWith("delete")) { + try { + String noSpace = trimmedInput.replaceAll("\\s",""); + int index = Integer.parseInt(noSpace.substring(6)) - 1; + taskList.deleteTask(index); + ui.showTaskDeleted(); + storage.saveTasks(taskList.getTasks()); + } catch (NumberFormatException | IndexOutOfBoundsException e) { + ui.showError("Invalid task number for deletion! (´;︵;`)"); + } + } else if (trimmedInput.startsWith("todo")) { + String description = trimmedInput.substring(4).trim(); + if (description.isEmpty()) { + ui.showError("The description of a todo cannot be empty. (。•́︿•̀。)"); + } else { + Task task = new Todo(description); + taskList.addTask(task); + ui.showTaskAdded(task, taskList.size()); + storage.saveTasks(taskList.getTasks()); + } + } else if (trimmedInput.startsWith("deadline")) { + String[] parts = trimmedInput.substring(8).split("/by", 2); + if (parts.length < 2 || parts[0].trim().isEmpty() || parts[1].trim().isEmpty()) { + ui.showError("Your deadline task needs a description and a due date.\nFormat: deadline /by "); + } else { + String description = parts[0].trim(); + String by = parts[1].trim(); + Task task = new Deadline(description, by); + taskList.addTask(task); + ui.showTaskAdded(task, taskList.size()); + storage.saveTasks(taskList.getTasks()); + } + } else if (trimmedInput.startsWith("event")) { + // Expecting format: event /from /to + String[] parts = trimmedInput.substring(5).split("/from|/to"); + if (parts.length < 3 || + parts[0].trim().isEmpty() || + parts[1].trim().isEmpty() || + parts[2].trim().isEmpty()) { + ui.showError("Your event task needs a description and times.\nFormat: event /from /to "); + } else { + String description = parts[0].trim(); + String from = parts[1].trim(); + String to = parts[2].trim(); + Task task = new Event(description, from, to); + taskList.addTask(task); + ui.showTaskAdded(task, taskList.size()); + storage.saveTasks(taskList.getTasks()); + } + } else { + ui.showError("I'm sorry, but I don't know what that means. (・・)?"); + } + return false; + } +} + diff --git a/src/main/java/Rick.java b/src/main/java/Rick.java new file mode 100644 index 000000000..0e6b43a84 --- /dev/null +++ b/src/main/java/Rick.java @@ -0,0 +1,21 @@ +/** + * The main entry point of the application. + */ + + + +public class Rick { + public static void main(String[] args) { + Ui ui = new Ui(); + Storage storage = new Storage(); + TaskList taskList = new TaskList(storage.loadTasks()); + + ui.showWelcome(); + + boolean exit = false; + while (!exit) { + String input = ui.readCommand(); + exit = Parser.parse(input, taskList, storage, ui); + } + } +} diff --git a/src/main/java/RickException.java b/src/main/java/RickException.java new file mode 100644 index 000000000..322d7cf1c --- /dev/null +++ b/src/main/java/RickException.java @@ -0,0 +1,3 @@ +public class RickException extends Exception{ + //empty; +} diff --git a/src/main/java/Storage.java b/src/main/java/Storage.java new file mode 100644 index 000000000..e8c883d96 --- /dev/null +++ b/src/main/java/Storage.java @@ -0,0 +1,92 @@ +import java.io.*; +import java.util.ArrayList; +import java.util.Scanner; + +/** + * Stores task elements in hard drive + */ +public class Storage { + private File dataFolder; + private File dataFile; + + /** + * The constructor creates a folder named "data" in the current directory and a file named "Rick.txt" within that folder. + * If the folder or file does not exist, they are automatically created. + */ + public Storage() { + // Relative path: "./data/duke.txt" + dataFolder = new File("data"); // folder named 'data' + dataFile = new File(dataFolder, "Rick.txt"); // file named 'Rick.txt' in that folder + + // Create the folder if it doesn't exist + if (!dataFolder.exists()) { + dataFolder.mkdirs(); + } + + // Create the file if it doesn't exist + try { + if (!dataFile.exists()) { + dataFile.createNewFile(); + } + } catch (IOException e) { + System.out.println("Error creating file: " + e.getMessage()); + } + } + + + public ArrayList loadTasks() { + ArrayList tasks = new ArrayList<>(); + try (Scanner fileScanner = new Scanner(dataFile)) { + while (fileScanner.hasNextLine()) { + String line = fileScanner.nextLine(); + // Example line: "T | 1 | read book" + String[] parts = line.split("\\|"); + // Trim each part to remove leading/trailing spaces + for (int i = 0; i < parts.length; i++) { + parts[i] = parts[i].trim(); + } + // parts[0] = T/D/E, parts[1] = 1/0, the rest are description/time + switch (parts[0]) { + case "T": + Todo todo = new Todo(parts[2]); + if (parts[1].equals("1")) { + todo.markAsDone(); + } + tasks.add(todo); + break; + case "D": + Deadline deadline = new Deadline(parts[2], parts[3]); + if (parts[1].equals("1")) { + deadline.markAsDone(); + } + tasks.add(deadline); + break; + case "E": + Event event = new Event(parts[2], parts[3], parts[4]); + if (parts[1].equals("1")) { + event.markAsDone(); + } + tasks.add(event); + break; + default: + // Unrecognized type + System.out.println("Warning: Unrecognized task type in file: " + line); + } + } + } catch (FileNotFoundException e) { + System.out.println("Error: File not found - " + e.getMessage()); + } + return tasks; + } + + + public void saveTasks(ArrayList tasks) { + try (PrintWriter writer = new PrintWriter(new FileWriter(dataFile))) { + for (Task task : tasks) { + writer.println(task.toDataString()); + } + } catch (IOException e) { + System.out.println("Error saving tasks: " + e.getMessage()); + } + } +} diff --git a/src/main/java/Task.java b/src/main/java/Task.java new file mode 100644 index 000000000..88497edbe --- /dev/null +++ b/src/main/java/Task.java @@ -0,0 +1,36 @@ + +/** + * The Task class provides basic functionalities for handling a task's description, + * marking it as done or undone, and generating string representations of the task. + */ + + +public class Task { + protected String description; + protected boolean isDone; + + public Task(String description) { + this.description = description; + this.isDone = false; + } + + public String getStatusIcon() { + return (isDone ? "X" : " "); // mark done task with X + } + + public void markAsDone(){ + this.isDone = true; + } + + public void markAsUndone(){ + this.isDone = false; + } + @Override + public String toString(){ + return "[" + getStatusIcon() +"] " + description; + } + public String toDataString() { + return ""; + } + +} \ No newline at end of file diff --git a/src/main/java/TaskList.java b/src/main/java/TaskList.java new file mode 100644 index 000000000..f45d07ea2 --- /dev/null +++ b/src/main/java/TaskList.java @@ -0,0 +1,47 @@ +import java.util.ArrayList; +/** + * The TaskList class encapsulates a collection of Task objects + */ + +public class TaskList { + private ArrayList tasks; + + public TaskList() { + this.tasks = new ArrayList<>(); + } + + public TaskList(ArrayList tasks) { + this.tasks = tasks; + } + + public ArrayList getTasks() { + return tasks; + } + + public void addTask(Task task) { + tasks.add(task); + } + + public Task deleteTask(int index) { + return tasks.remove(index); + } + + public Task getTask(int index) { + return tasks.get(index); + } + + public int size() { + return tasks.size(); + } + + public ArrayList findTasks(String keyword) { + ArrayList matchingTasks = new ArrayList<>(); + for (Task task : tasks) { + if (task.description.toLowerCase().contains(keyword.toLowerCase())) { + matchingTasks.add(task); + } + } + return matchingTasks; + } +} + diff --git a/src/main/java/Todo.java b/src/main/java/Todo.java new file mode 100644 index 000000000..dd789b202 --- /dev/null +++ b/src/main/java/Todo.java @@ -0,0 +1,14 @@ +public class Todo extends Task{ + public Todo(String description){ + super(description); + } + @Override + public String toString(){ + return "[T]" + super.toString(); + } + @Override + public String toDataString() { + // Format: T | 1/0 | description + return "T | " + (isDone ? "1" : "0") + " | " + this.description; + } +} diff --git a/src/main/java/Ui.java b/src/main/java/Ui.java new file mode 100644 index 000000000..38aadba26 --- /dev/null +++ b/src/main/java/Ui.java @@ -0,0 +1,105 @@ +import java.util.ArrayList; +import java.util.Scanner; +/** + * Handles user interactions by displaying messages and reading user input. + * The Ui class is responsible for printing the welcome message, task updates, + * and errors. It also reads commands entered by the user. + */ +public class Ui { + + private static final String DIVIDER = " ____________________________________________________________"; + + private Scanner scanner; + + public Ui() { + scanner = new Scanner(System.in); + } + + public void showWelcome() { + String logo = + " ____ _ _ \n" + + "| _ \\(_) ___| | __\n" + + "| |_) | |/ __| |/ /\n" + + "| _ <| | (__| < \n" + + "|_| \\_\\_|\\___|_|\\_\\"; + System.out.println("Hello from\n" + logo); + System.out.println("Hello, I'm Rick (´。• ᵕ •。`)\nWhat can I do for you?"); + showDivider(); + } + + public void showGoodbye() { + System.out.println("bye bye! Hope to see you soon (❁´◡`❁)"); + showDivider(); + } + + public String readCommand() { + System.out.print(">"); + return scanner.nextLine(); + } + + public void showDivider() { + System.out.println(DIVIDER); + } + + public void showError(String message) { + System.out.println(message); + showDivider(); + } + + public void showMessage(String message) { + System.out.println(message); + showDivider(); + } + + public void showTaskAdded(Task task, int totalTasks) { + System.out.println("Okie doki, added to task! ʕ•ᴥ•ʔ "); + System.out.println(task); + System.out.println("Now you have " + totalTasks + " tasks in list ᕙ(⇀‸↼‶)ᕗ"); + showDivider(); + } + + public void showTaskDone(Task task) { + System.out.println("Nicee! I've marked this task as done ᕕ( ᐛ )ᕗ :"); + System.out.println(task); + showDivider(); + } + + public void showTaskUndone(Task task) { + System.out.println("Gotcha! I've unmarked this task (≧▽≦)"); + System.out.println(task); + showDivider(); + } + + public void showTaskDeleted() { + System.out.println("okie deleted! (`・ω・´)"); + showDivider(); + } + + public void showTaskList(ArrayList tasks) { + if (tasks.isEmpty()) { + System.out.println(" No tasks added (;ↀ⌓ↀ)"); + } else { + System.out.println("Here are the items in your list ᕦ(ò_óˇ)ᕤ :"); + int index = 1; + for (Task task : tasks) { + System.out.println(index + ". " + task); + index++; + } + } + showDivider(); + } + public void showFoundTasks(ArrayList tasks) { + if (tasks.isEmpty()) { + System.out.println("No matching tasks found. (╥_╥)"); + } else { + System.out.println("Here are the matching tasks in your list (ง'̀-'́)ง :"); + int index = 1; + for (Task task : tasks) { + System.out.println(index + ". " + task); + index++; + } + } + showDivider(); + } +} +