diff --git a/data/conglo.txt b/data/conglo.txt new file mode 100644 index 000000000..e69de29bb diff --git a/docs/README.md b/docs/README.md index 47b9f984f..97d3dc466 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,30 +1,101 @@ -# Duke User Guide +# Conglo Task Manager Chatbot -// Update the title above to match the actual product name +Conglo is a command-line chatbot designed to help you manage tasks efficiently. Whether it's keeping track of todos, deadlines, or events, +Conglo provides simple commands to organize your tasks, making it easier to stay productive. -// Product screenshot goes here +## Features -// Product intro goes here +1. Add tasks, deadlines, and events. +2. Mark tasks as done or unmark them. +3. Delete tasks you no longer need. +4. View all tasks or search for tasks using keywords. +5. Persistent task storage: Tasks are saved locally, allowing the chatbot to reload them automatically the next time the application is launched. +6. Exit the application smoothly by saying 'bye'. -## Adding deadlines +## Commands -// Describe the action and its outcome. +1. **Add a todo** + ```plaintext + todo [description] + ``` + Example: todo Buy groceries -// Give examples of usage +2. **Add a deadline** + ```plaintext + deadline [description] /by [dd-MM-yyyy HHmm] + ``` + Example: deadline Submit report /by 12-10-2024 2359 -Example: `keyword (optional arguments)` +3. **Add an event** + ```plaintext + event [description] /from [start time] /to [end time] + ``` + Example: event Team meeting /from 13-10-2024 0900 /to 13-10-2024 1000 -// A description of the expected outcome goes here +4. **Mark a task as done** + ```plaintext + mark [task number] + ``` + Example: mark 1 -``` -expected output -``` +5. **Unmark a task** + ```plaintext + unmark [task number] + ``` + Example: unmark 1 -## Feature ABC +6. **Delete a task** + ```plaintext + delete [task number] + ``` + Example: delete 2 -// Feature details +7. **List all tasks** + ```plaintext + list + ``` +8. **Find tasks by keyword** + ```plaintext + find [keyword] + ``` + Example: find groceries -## Feature XYZ +9. **Exit the application** + ```plaintext + bye + ``` -// Feature details \ No newline at end of file +## Prerequisites + +* JDK 17 +* Conglo JAR file (Ensure you have the compiled JAR file to run the application) + +## Setup Instructions + +1. Download the Conglo JAR file to your local machine. +2. Open a command prompt or terminal in the directory where the JAR file is located. +3. Run the application using the following command: + ```plaintext + java -jar Conglo.jar + ``` +4. If the setup is correct, you should see the following output: + + ```plaintext + ---------------------------------------------------------- + Hola! I'm Conglo, the friendly task manager. + ---------------------------------------------------------- + Here's a quick manual to get you started: + 1. Add a todo: todo [description] + 2. Add a deadline: deadline [description] /by [dd-MM-yyyy HHmm] + 3. Add an event: event [description] /from [start time] /to [end time] + 4. Mark a task as done: mark [task number] + 5. Unmark a task: unmark [task number] + 6. Delete a task: delete [task number] + 7. List all tasks: list + 8. Find matching keyword: find [keyword] + 9. Exit the application: bye + ---------------------------------------------------------- + The list is empty! + ---------------------------------------------------------- + ``` diff --git a/src/main/java/Conglo.class b/src/main/java/Conglo.class new file mode 100644 index 000000000..43955f6cd Binary files /dev/null and b/src/main/java/Conglo.class differ 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/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..4faa854c5 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: conglo.Conglo + diff --git a/src/main/java/conglo/Conglo.java b/src/main/java/conglo/Conglo.java new file mode 100644 index 000000000..2869858fd --- /dev/null +++ b/src/main/java/conglo/Conglo.java @@ -0,0 +1,73 @@ +package conglo; + +import conglo.exception.StorageInvalidFormat; +import conglo.ui.Ui; +import conglo.exception.CongloException; +import conglo.storage.Storage; +import conglo.task.TaskList; +import conglo.command.Parser; + +import java.io.IOException; + +/** + * Main class for the Conglo chatbot. + * Initializes the application, loads tasks, and handles user interactions. + */ +public class Conglo { + + private Ui ui; + private TaskList taskList; + private Storage storage; + + /** + * Constructs a Conglo instance with the specified file path for task storage. + * Loads existing tasks or initializes a new task list if loading fails. + * + * @param filePath the file path to load tasks from and save tasks to + */ + public Conglo(String filePath) { + ui = new Ui(); + storage = new Storage(filePath); + try { + storage.createFileIfNotExists(); + taskList = new TaskList(storage.loadTasks()); + } catch (StorageInvalidFormat | IOException e) { + ui.displayLoadingError(); + taskList = new TaskList(); + } + } + + /** + * Runs the main loop of the application, processing user input until the 'bye' command is given. + */ + public void run() { + ui.greetUser(); + String input; + boolean isQuit = false; + + while (!isQuit) { + input = ui.getUserInput(); + if (input.equals("bye")) { + isQuit = true; + ui.sayGoodbye(); + } + try { + Parser.processCommand(taskList, input); + } catch (CongloException e) { + System.out.println(e.getMessage()); + } + ui.printLineSeparator(); + } + ui.closeScanner(); + } + + /** + * The main entry point of the Conglo application. + * Initializes the application and starts the main run loop. + * + * @param args command-line arguments + */ + public static void main(String[] args) { + new Conglo("./data/conglo.txt").run(); + } +} diff --git a/src/main/java/conglo/command/DateParser.java b/src/main/java/conglo/command/DateParser.java new file mode 100644 index 000000000..3f4835644 --- /dev/null +++ b/src/main/java/conglo/command/DateParser.java @@ -0,0 +1,51 @@ +package conglo.command; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +/** + * Utility class for parsing and formatting dates and times. + */ +public class DateParser { + + /** + * Parses a date and time string into a {@link LocalDateTime} object. + * The input string should follow the format "dd-MM-yyyy HHmm", where: + * + * + * @param dateStr The date and time string to be parsed. + * @return A {@link LocalDateTime} object representing the parsed date and time. + * @throws DateTimeParseException If the input string is not in the correct format. + */ + public static LocalDateTime parseDateTime(String dateStr) throws DateTimeParseException { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HHmm"); + return LocalDateTime.parse(dateStr, formatter); + } + + /** + * Formats a {@link LocalDateTime} object into a readable date and time string. + * The formatted output will be in the format "d MMM yyyy hh:mm a", where: + * + * + * @param dateTime The {@link LocalDateTime} object to be formatted. + * @return A string representing the formatted date and time. + */ + public static String formatDateTime(LocalDateTime dateTime) { + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("d MMM yyyy"); + DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm a"); + + String formattedDate = dateTime.format(dateFormatter); + String formattedTime = dateTime.format(timeFormatter); + + return formattedDate + " " + formattedTime; + } +} diff --git a/src/main/java/conglo/command/Parser.java b/src/main/java/conglo/command/Parser.java new file mode 100644 index 000000000..6ae8dd592 --- /dev/null +++ b/src/main/java/conglo/command/Parser.java @@ -0,0 +1,231 @@ +package conglo.command; + +import conglo.exception.CongloException; +import conglo.exception.InvalidTaskNumber; +import conglo.exception.InvalidFormat; +import conglo.exception.MissingDescription; +import conglo.exception.MissingTaskNumber; +import conglo.exception.UnknownCommand; +import conglo.storage.Storage; +import conglo.task.Deadline; +import conglo.task.Event; +import conglo.task.Task; +import conglo.task.Todo; +import conglo.task.TaskList; +import conglo.ui.Ui; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; + +/** + * Handles parsing and processing of user commands for task management. + */ +public class Parser { + private static boolean isDelete = false; + private static Ui ui = new Ui(); + + private static void saveTasks(TaskList taskList) { + Storage.saveTasks(taskList); + } + + /** + * Displays a message about the added or removed task and the current number of tasks. + * + * @param taskList The list of tasks. + * @param task The task that was added or removed. + */ + public static void echoTask(TaskList taskList, Task task) { + int size = taskList.getSize(); + if (isDelete) { + ui.displayRemoved(); + size--; + } else { + ui.displayAdded(); + } + String taskSuffix = " task"; + if (size > 1) { + taskSuffix = " tasks"; + } + System.out.println(" " + task.toFileFormat()); + System.out.println("The list has " + size + taskSuffix + " now."); + } + + /** + * Displays the current list of tasks. + */ + public static void listTasks() { + ui.displayTaskList(); + } + + /** + * Marks a task as done or not done based on user input. + * + * @param taskList The list of tasks. + * @param words The command and task number input by the user. + * @throws InvalidTaskNumber If the task number is invalid. + */ + public static void markTask(TaskList taskList, String[] words) throws InvalidTaskNumber { + int taskNumber; + try { + taskNumber = Integer.parseInt(words[1]) - 1; + } catch (NumberFormatException e) { + System.out.println("Invalid format! Please provide a task number >.<"); + return; + } + + if (taskNumber >= taskList.getSize() || taskNumber < 0) { + throw new InvalidTaskNumber(); + } + if (words[0].equals("mark")) { + taskList.getTask(taskNumber).markAsDone(); + System.out.println("Nice! I've marked this task as done:"); + } else { + taskList.getTask(taskNumber).markAsNotDone(); + System.out.println("OK, I've marked this task as not done yet:"); + } + System.out.println(taskList.getTask(taskNumber).toFileFormat()); + saveTasks(taskList); + } + + /** + * Adds a new To-do task to the task list. + * + * @param taskList The list of tasks. + * @param sentence The description of the To-do task. + */ + public static void addTodo(TaskList taskList, String sentence) { + Todo todo = new Todo(sentence); + taskList.addTask(todo); + echoTask(taskList, todo); + saveTasks(taskList); + } + + /** + * Adds a new Deadline task to the task list. + * + * @param taskList The list of tasks. + * @param sentence The description and due date of the Deadline task. + * @throws InvalidFormat If the format of the deadline is incorrect. + */ + public static void addDeadline(TaskList taskList, String sentence) throws InvalidFormat { + if (!sentence.contains(" /by ")) { + throw new InvalidFormat("deadline"); + } + String[] words = sentence.split(" /by "); + LocalDateTime dueDate; + try { + dueDate = DateParser.parseDateTime(words[1]); + } catch (DateTimeParseException e) { + System.out.println("Invalid date format! Please use the format 'dd-MM-yyyy HHmm'."); + return; + } + String dateTime = DateParser.formatDateTime(dueDate); + Deadline deadline = new Deadline(words[0], dateTime); + taskList.addTask(deadline); + echoTask(taskList, deadline); + saveTasks(taskList); + } + + /** + * Adds a new Event task to the task list. + * + * @param taskList The list of tasks. + * @param sentence The description, start, and end time of the Event task. + * @throws InvalidFormat If the format of the event is incorrect. + */ + public static void addEvent(TaskList taskList, String sentence) throws InvalidFormat { + if (!sentence.contains(" /from ") || !sentence.contains(" /to ")) { + throw new InvalidFormat("event"); + } + String[] words = sentence.split(" /from | /to "); + Event event = new Event(words[0], words[1], words[2]); + taskList.addTask(event); + echoTask(taskList, event); + saveTasks(taskList); + } + + /** + * Deletes a task from the task list based on user input. + * + * @param taskList The list of tasks. + * @param word The task number to delete. + * @throws InvalidTaskNumber If the task number is invalid. + */ + public static void deleteTask(TaskList taskList, String word) throws InvalidTaskNumber { + int index; + try { + index = Integer.parseInt(word) - 1; + } catch (NumberFormatException e) { + System.out.println("Invalid format! Please provide a task number >.<"); + return; + } + if (index >= taskList.getSize() || index < 0) { + throw new InvalidTaskNumber(); + } + isDelete = true; + echoTask(taskList, taskList.getTask(index)); + taskList.removeTask(index); + isDelete = false; + saveTasks(taskList); + } + + /** + * Processes the user command and calls the appropriate methods to handle the command. + * + * @param taskList The list of tasks. + * @param command The user command input. + * @throws CongloException If an error occurs while processing the command. + */ + public static void processCommand(TaskList taskList, String command) throws CongloException { + String[] words = command.split(" ", 2); + switch(words[0]) { + case "bye": + break; + case "list": + if (words.length > 1) { + throw new InvalidFormat("list"); + } + listTasks(); + break; + case "find": + if (words.length == 1 || words[1].isEmpty()) { + throw new MissingDescription("find"); + } + ui.displayFoundTasks(words[1]); + break; + case "unmark": + case "mark": + if (words.length == 1 || words[1].isEmpty()) { + throw new MissingTaskNumber(words[0]); + } + markTask(taskList, words); + break; + case "delete": + if (words.length == 1 || words[1].isEmpty()) { + throw new MissingTaskNumber(words[0]); + } + deleteTask(taskList, words[1]); + break; + case "todo": + if (words.length == 1 || words[1].isEmpty()) { + throw new MissingDescription("Todo"); + } + addTodo(taskList, words[1]); + break; + case "deadline": + if (words.length == 1 || words[1].isEmpty()) { + throw new MissingDescription("deadline"); + } + addDeadline(taskList, words[1]); + break; + case "event": + if (words.length == 1 || words[1].isEmpty()) { + throw new MissingDescription("event"); + } + addEvent(taskList, words[1]); + break; + default: + throw new UnknownCommand(); + } + } +} diff --git a/src/main/java/conglo/exception/CongloException.java b/src/main/java/conglo/exception/CongloException.java new file mode 100644 index 000000000..90ec73a92 --- /dev/null +++ b/src/main/java/conglo/exception/CongloException.java @@ -0,0 +1,14 @@ +package conglo.exception; + +/** + * Represents a custom exception for errors specific to the Conglo application. + */ +public class CongloException extends Exception { + + /** + * Constructs a new CongloException with the specified detail message. + */ + public CongloException(String message) { + super(message); + } +} diff --git a/src/main/java/conglo/exception/InvalidFormat.java b/src/main/java/conglo/exception/InvalidFormat.java new file mode 100644 index 000000000..2873581fc --- /dev/null +++ b/src/main/java/conglo/exception/InvalidFormat.java @@ -0,0 +1,40 @@ +package conglo.exception; + +/** + * Represents an exception thrown when a user inputs a command in an incorrect format. + */ +public class InvalidFormat extends CongloException { + + /** + * Constructs a new InvalidFormat exception with a specific error message + * based on the type of command that was formatted incorrectly. + * + * @param type the type of command that was incorrectly formatted + */ + public InvalidFormat(String type) { + super(generateMessage(type)); + } + + /** + * Generates a error message based on the type of the incorrectly + * formatted command. + * + * @param type The type of command. + * @return a string containing the error message suggesting the correct format. + */ + private static String generateMessage(String type) { + switch (type) { + case "list": + return "Do you mean 'list'?"; + case "deadline": + return "Oh dear, your deadline command is a bit off." + + System.lineSeparator() + "Please use: deadline [description] /by [deadline]."; + case "event": + return "Oh no! Your event command needs a bit of tweaking." + + System.lineSeparator() + "Try: event [description] /from [start time] /to [end time]."; + default: + return "Oops! Looks like there's a hiccup in your command." + + System.lineSeparator() + "Please follow the correct format."; + } + } +} diff --git a/src/main/java/conglo/exception/InvalidTaskNumber.java b/src/main/java/conglo/exception/InvalidTaskNumber.java new file mode 100644 index 000000000..ce251cdb6 --- /dev/null +++ b/src/main/java/conglo/exception/InvalidTaskNumber.java @@ -0,0 +1,16 @@ +package conglo.exception; + +/** + * Represents an exception thrown when the user provides an invalid task number, + * such as when the task number is out of range or improperly formatted. + */ +public class InvalidTaskNumber extends CongloException { + + /** + * Constructs a new InvalidTaskNumber exception with a default error message + * indicating that the task number is invalid. + */ + public InvalidTaskNumber() { + super("Invalid task number! Please try again."); + } +} diff --git a/src/main/java/conglo/exception/MissingDescription.java b/src/main/java/conglo/exception/MissingDescription.java new file mode 100644 index 000000000..75d0b5f3e --- /dev/null +++ b/src/main/java/conglo/exception/MissingDescription.java @@ -0,0 +1,18 @@ +package conglo.exception; + +/** + * Represents an exception thrown when a required description is missing + * from the user's input, such as when adding a task without specifying details. + */ +public class MissingDescription extends CongloException { + + /** + * Constructs a new MissingDescription exception with an error message + * that indicates the type of task that is missing a description. + * + * @param type The type of task or command missing a description. + */ + public MissingDescription(String type) { + super("Oopsies, please add a description of " + type + "."); + } +} diff --git a/src/main/java/conglo/exception/MissingTaskNumber.java b/src/main/java/conglo/exception/MissingTaskNumber.java new file mode 100644 index 000000000..b38a8f766 --- /dev/null +++ b/src/main/java/conglo/exception/MissingTaskNumber.java @@ -0,0 +1,18 @@ +package conglo.exception; + +/** + * Represents an exception thrown when a task number is missing from the user's input, + * particularly when attempting to mark, unmark, or delete a task. + */ +public class MissingTaskNumber extends CongloException { + + /** + * Constructs a new MissingTaskNumber exception with an error message that + * indicates the action that requires a task number. + * + * @param action The action being performed that requires a task number. + */ + public MissingTaskNumber(String action) { + super("Please provide task number to " + action + "."); + } +} diff --git a/src/main/java/conglo/exception/StorageInvalidFormat.java b/src/main/java/conglo/exception/StorageInvalidFormat.java new file mode 100644 index 000000000..610b018dc --- /dev/null +++ b/src/main/java/conglo/exception/StorageInvalidFormat.java @@ -0,0 +1,16 @@ +package conglo.exception; + +/** + * Represents an exception thrown when the storage file contains an invalid task format. + */ +public class StorageInvalidFormat extends CongloException { + + /** + * Constructs a StorageInvalidFormat exception with the specified invalid line. + * + * @param line The line from the storage file that contains an invalid task format. + */ + public StorageInvalidFormat(String line) { + super("Invalid task format: " + line); + } +} diff --git a/src/main/java/conglo/exception/UnknownCommand.java b/src/main/java/conglo/exception/UnknownCommand.java new file mode 100644 index 000000000..428d66352 --- /dev/null +++ b/src/main/java/conglo/exception/UnknownCommand.java @@ -0,0 +1,14 @@ +package conglo.exception; + +/** + * Represents an exception thrown when a user inputs an unrecognized command. + */ +public class UnknownCommand extends CongloException { + + /** + * Constructs an UnknownCommand exception with a message. + */ + public UnknownCommand() { + super("Sorry, I'm not smart enough to understand what you mean."); + } +} diff --git a/src/main/java/conglo/storage/Storage.java b/src/main/java/conglo/storage/Storage.java new file mode 100644 index 000000000..1ed860aff --- /dev/null +++ b/src/main/java/conglo/storage/Storage.java @@ -0,0 +1,148 @@ +package conglo.storage; + +import conglo.task.Task; +import conglo.task.TaskList; +import conglo.task.Todo; +import conglo.task.Deadline; +import conglo.task.Event; +import conglo.exception.StorageInvalidFormat; + +import java.io.BufferedWriter; +import java.io.FileNotFoundException; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Scanner; + +/** + * Handles loading and saving tasks from a file to provide persistent storage. + */ +public class Storage { + + private static String filePath; + + /** + * Initializes the storage with the specified file path. + * + * @param filePath The path of the file to load and save tasks. + */ + public Storage(String filePath) { + Storage.filePath = filePath; + } + + /** + * Loads tasks from the storage file and returns them as an ArrayList. + * + * @return an ArrayList of tasks loaded from the file. + * @throws StorageInvalidFormat If the file format is invalid. + * @throws FileNotFoundException If the specified file does not exist. + */ + public ArrayList loadTasks() throws StorageInvalidFormat, FileNotFoundException { + ArrayList tasks = new ArrayList<>(); + try { + File file = new File(filePath); + if (!file.exists()) { + throw new FileNotFoundException(filePath); + } + Scanner scanner = new Scanner(file); + while (scanner.hasNext()) { + String line = scanner.nextLine(); + String[] parts = line.split(" \\| "); + + if (parts.length < 3) { + throw new StorageInvalidFormat(line); + } + + String type = parts[0]; + boolean isDone = parts[1].equals("1"); + String description = parts[2]; + + Task task = null; + switch (type) { + case "T": + task = new Todo(description); + break; + case "D": + validatePartsLength(parts, line); + String deadlineTime = parts[3].substring(3); + task = new Deadline(description, deadlineTime); + break; + case "E": + validatePartsLength(parts, line); + String[] details = extractEventDetails(parts[3]); + task = new Event(description, details[0], details[1]); + break; + } + if (task != null && isDone) { + task.markAsDone(); + } + tasks.add(task); + } + scanner.close(); + } catch (IOException e) { + System.out.println("Error loading tasks: " + e.getMessage()); + } + return tasks; + } + + /** + * Validates if the parts array has the required length. + * Throws StorageInvalidFormat if invalid. + * + * @param parts The array of string parts. + * @param line The original line for the error message. + * @throws StorageInvalidFormat if the length is insufficient. + */ + private void validatePartsLength(String[] parts, String line) throws StorageInvalidFormat { + if (parts.length < 4) { + throw new StorageInvalidFormat(line); + } + } + + /** + * Extracts the event details (start and end time) from the event part string. + * + * @param eventPart The string containing the event times (from-to format). + * @return A String array containing the start and end times. + */ + private String[] extractEventDetails(String eventPart) { + String[] details = eventPart.split(" to "); + details[0] = details[0].substring(5); // Clean start time + return details; + } + + /** + * Saves the current list of tasks to the storage file. + * + * @param taskList The TaskList object containing the tasks to be saved. + */ + public static void saveTasks(TaskList taskList) { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) { + for (Task task : taskList.getTaskList()) { + writer.write(task.toFileFormat()); + writer.newLine(); + } + } catch (IOException e) { + System.out.println("Error saving tasks: " + e.getMessage()); + } + } + + /** + * Ensures that a file at the specified path exists. + * If the file does not exist, it will be created. + * If the parent directory does not exist, it will be created as well. + * + * @throws IOException if an I/O error occurs while creating the file or directory. + */ + public void createFileIfNotExists() throws IOException { + File file = new File(filePath); + if (!file.exists()) { + File parentDir = file.getParentFile(); + if (parentDir != null && !parentDir.exists()) { + parentDir.mkdirs(); + } + file.createNewFile(); + } + } +} diff --git a/src/main/java/conglo/task/Deadline.java b/src/main/java/conglo/task/Deadline.java new file mode 100644 index 000000000..06b838cf0 --- /dev/null +++ b/src/main/java/conglo/task/Deadline.java @@ -0,0 +1,37 @@ +package conglo.task; + +/** + * Represents a deadline task with a description and a deadline date. + */ +public class Deadline extends Task { + + protected String by; + + public Deadline(String description, String by) { + super(description); + this.by = by; + } + + @Override + protected String getTaskType() { + return "D"; + } + + /** + * Returns a formatted string of the details for this task. + * + * @return A string representing the deadline for the task. + */ + protected String getFormattedDetails() { + return "by " + by; + } + + @Override + public String toFileFormat() { + return String.format("%s | %s | %s | %s", + getTaskType(), + getStatusIcon(), + getDescription(), + getFormattedDetails()); + } +} diff --git a/src/main/java/conglo/task/Event.java b/src/main/java/conglo/task/Event.java new file mode 100644 index 000000000..9e6ab767e --- /dev/null +++ b/src/main/java/conglo/task/Event.java @@ -0,0 +1,57 @@ +package conglo.task; + +/** + * Represents an event task with a description, start date, and end date. + */ +public class Event extends Task { + + protected String start; + protected String end; + + /** + * Constructs an Event task with the specified description, start date, and end date. + * + * @param description The description of the event. + * @param start The start date of the event. + * @param end The end date of the event. + */ + public Event(String description, String start, String end) { + super(description); + this.start = start; + this.end = end; + } + + public String getStart() { + return start; + } + + public String getEnd() { + return end; + } + + @Override + protected String getTaskType() { + return "E"; + } + + /** + * Returns a string representation of the Event task. + * The format includes a label for events, the task's completion status, + * description, and the start and end dates of the event. + * + * @return A string representation of the Event task. + */ + protected String getFormattedDetails() { + return "from " + start + " to " + end; + } + + @Override + public String toFileFormat() { + return String.format("%s | %s | %s | from %s to %s", + getTaskType(), + getStatusIcon(), + getDescription(), + getStart(), + getEnd()); + } +} diff --git a/src/main/java/conglo/task/Task.java b/src/main/java/conglo/task/Task.java new file mode 100644 index 000000000..6b2b0cea5 --- /dev/null +++ b/src/main/java/conglo/task/Task.java @@ -0,0 +1,70 @@ +package conglo.task; + +/** + * Represents a task with a description and a completion status. + */ +public abstract class Task { + protected String description; + protected boolean isDone; + + /** + * Constructs a Task with the specified description. + * The task is initially marked as not done. + * + * @param description Description of the task to be created. + */ + public Task(String description) { + this.description = description; + this.isDone = false; + } + + /** + * Returns a status icon representing the task's completion status. + * "1" indicates that the task is done; "0" indicates it is not done. + * + * @return A status icon ("1" if done, otherwise "0"). + */ + public String getStatusIcon() { + if (isDone) { + return "1"; + } + return "0"; + } + + /** + * Marks the task as done. + */ + public void markAsDone() { + isDone = true; + } + + /** + * Marks the task as not done. + */ + public void markAsNotDone() { + isDone = false; + } + + /** + * Returns the description of the task. + */ + public String getDescription() { + return description; + } + + /** + * Returns the string representation of the task in a file format. + */ + public abstract String toFileFormat(); + + /** + * Returns the type of the task. + */ + protected abstract String getTaskType(); + + /** + * Returns the formatted details of the task. + */ + protected abstract String getFormattedDetails(); + +} diff --git a/src/main/java/conglo/task/TaskList.java b/src/main/java/conglo/task/TaskList.java new file mode 100644 index 000000000..8fb56a6b1 --- /dev/null +++ b/src/main/java/conglo/task/TaskList.java @@ -0,0 +1,95 @@ +package conglo.task; + +import java.util.ArrayList; + +/** + * Represents a list of tasks. + */ +public class TaskList { + private static ArrayList taskList; + + /** + * Constructs a TaskList with the specified list of tasks. + * + * @param tasks The initial list of tasks. + */ + public TaskList(ArrayList tasks) { + taskList = tasks; + } + + /** + * Default constructor for an empty task list. + * Initializes the task list with an empty list. + */ + public TaskList() { + taskList = new ArrayList<>(); + } + + /** + * Checks if the task list is empty. + */ + public static boolean isEmpty() { + return taskList.isEmpty(); + } + + /** + * Adds a task to the task list. + * + * @param task The task to be added. + */ + public void addTask(Task task) { + taskList.add(task); + } + + /** + * Removes a task from the task list at the specified index. + * + * @param index The index of the task to be removed. + */ + public void removeTask(int index) { + taskList.remove(index); + } + + /** + * Retrieves the task at the specified index. + * + * @param index The index of the task to retrieve. + * @return The task at the specified index. + */ + public Task getTask(int index) { + return taskList.get(index); + } + + /** + * Returns the number of tasks in the list. + */ + public int getSize() { + return taskList.size(); + } + + /** + * Returns the list of tasks. + */ + public ArrayList getTaskList() { + return taskList; + } + + /** + * Lists all tasks in the task list, printing their details. + */ + public static void listTasks() { + for (int i = 0; i < taskList.size(); i++) { + System.out.println((i + 1) + ". " + taskList.get(i).toFileFormat()); + } + } + + public static ArrayList findTasks(String keyword) { + ArrayList matchingTasks = new ArrayList<>(); + for (Task task : taskList) { + if (task.getDescription().toLowerCase().contains(keyword.toLowerCase())) { + matchingTasks.add(task); + } + } + return matchingTasks; + } +} diff --git a/src/main/java/conglo/task/Todo.java b/src/main/java/conglo/task/Todo.java new file mode 100644 index 000000000..1cb00f5f5 --- /dev/null +++ b/src/main/java/conglo/task/Todo.java @@ -0,0 +1,34 @@ +package conglo.task; + +/** + * Represents a To-do task with a description. + */ +public class Todo extends Task { + + /** + * Constructs a To-do task with the specified description. + * + * @param description The description of the to-do task. + */ + public Todo(String description) { + super(description); + } + + @Override + protected String getTaskType() { + return "T"; + } + + @Override + protected String getFormattedDetails() { + return ""; + } + + @Override + public String toFileFormat() { + return String.format("%s | %s | %s", + getTaskType(), + getStatusIcon(), + getDescription()); + } +} diff --git a/src/main/java/conglo/ui/Manual.java b/src/main/java/conglo/ui/Manual.java new file mode 100644 index 000000000..604419d77 --- /dev/null +++ b/src/main/java/conglo/ui/Manual.java @@ -0,0 +1,29 @@ +package conglo.ui; + +/** + * Provides a manual with basic instructions for using the Conglo application. + */ +public class Manual { + + /** + * Prints a quick manual of instructions for the Conglo application. + */ + public static void printManual() { + String[] manual = { + "Here's a quick manual to get you started:", + "1. Add a todo: todo [description]", + "2. Add a deadline: deadline [description] /by [dd-MM-yyyy HHmm]", + "3. Add an event: event [description] /from [start time] /to [end time]", + "4. Mark a task as done: mark [task number]", + "5. Unmark a task: unmark [task number]", + "6. Delete a task: delete [task number]", + "7. List all tasks: list", + "8. Find matching keyword: find [keyword]", + "9. Exit the application: bye" + }; + + for (String line : manual) { + System.out.println(line); + } + } +} diff --git a/src/main/java/conglo/ui/Ui.java b/src/main/java/conglo/ui/Ui.java new file mode 100644 index 000000000..930f34743 --- /dev/null +++ b/src/main/java/conglo/ui/Ui.java @@ -0,0 +1,132 @@ +package conglo.ui; + +import conglo.task.Task; +import conglo.task.TaskList; + +import java.util.ArrayList; +import java.util.Scanner; + +/** + * Handles user interactions for task managing. + */ +public class Ui { + + private static final String LINE_SEPARATOR = "-----------------------------" + + "-----------------------------"; + private Scanner scanner; + + /** + * Constructs a Ui object, initializing the scanner for user input. + */ + public Ui() { + this.scanner = new Scanner(System.in); + } + + /** + * Retrieves user input from the console. + * + * @return The input string from the user. + */ + public String getUserInput() { + return scanner.nextLine(); + } + + /** + * Prints a line separator to the console. + */ + public void printLineSeparator() { + printText(LINE_SEPARATOR); + } + + /** + * Greets the user and displays the manual. + */ + public void greetUser() { + printLineSeparator(); + printText("Hola! I'm Conglo, the friendly task manager."); + printLineSeparator(); + Manual.printManual(); + printLineSeparator(); + displayTaskList(); + printLineSeparator(); + } + + /** + * Displays a message indicating that a task has been removed. + */ + public void displayRemoved() { + printText("Okie! Task is removed from list:"); + } + + /** + * Displays a message indicating that a task has been added. + */ + public void displayAdded() { + printText("All done! Task added to list:"); + } + + /** + * Displays a message indicating that the task list is empty. + */ + public void displayEmptyList() { + printText("The list is empty!"); + } + + /** + * Displays the current task list. + * If the list is empty, it shows a corresponding message. + */ + public void displayTaskList() { + if (TaskList.isEmpty()) { + displayEmptyList(); + } else { + printText("Here are your tasks:"); + TaskList.listTasks(); + } + } + + /** + * Displays tasks with matching keyword that user provided. + * + * @param keyword Used to match words in descriptions. + */ + public void displayFoundTasks(String keyword) { + ArrayList matchingTasks = TaskList.findTasks(keyword); + if (matchingTasks.isEmpty()) { + System.out.println("No task matches the keyword :("); + } else { + System.out.println("Tasks matched in your list:"); + for (int i = 0; i < matchingTasks.size(); i++) { + System.out.println((i + 1) + ". " + matchingTasks.get(i).toFileFormat()); + } + } + } + + /** + * Displays an error message when there is an issue loading tasks. + */ + public void displayLoadingError() { + printText("Oops! There was an error loading your tasks. Please try again."); + } + + /** + * Displays a goodbye message to the user. + */ + public void sayGoodbye() { + printText("Goodbye. See you next time!"); + } + + /** + * Prints a specified message to the console. + */ + public void printText(String message) { + System.out.println(message); + } + + /** + * Closes the scanner to prevent resource leaks. + */ + public void closeScanner() { + scanner.close(); + } +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e..e27a53d88 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,103 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - +---------------------------------------------------------- +Hola! I'm Conglo, the friendly task manager. +---------------------------------------------------------- +Here's a quick manual to get you started: +1. Add a todo: todo [description] +2. Add a deadline: deadline [description] /by [dd-MM-yyyy HHmm] +3. Add an event: event [description] /from [start time] /to [end time] +4. Mark a task as done: mark [task number] +5. Unmark a task: unmark [task number] +6. Delete a task: delete [task number] +7. List all tasks: list +8. Find matching keyword: find [keyword] +9. Exit the application: bye +---------------------------------------------------------- +The list is empty! +---------------------------------------------------------- +All done! Task added to list: + T | 0 | read +The list has 1 task now. +---------------------------------------------------------- +All done! Task added to list: + D | 0 | assignment | by 20 Jul 2024 02:15 pm +The list has 2 tasks now. +---------------------------------------------------------- +Nice! I've marked this task as done: +T | 1 | read +---------------------------------------------------------- +All done! Task added to list: + E | 0 | presentation | from Thurs 10am to 12pm +The list has 3 tasks now. +---------------------------------------------------------- +Here are your tasks: +1. T | 1 | read +2. D | 0 | assignment | by 20 Jul 2024 02:15 pm +3. E | 0 | presentation | from Thurs 10am to 12pm +---------------------------------------------------------- +Nice! I've marked this task as done: +D | 1 | assignment | by 20 Jul 2024 02:15 pm +---------------------------------------------------------- +OK, I've marked this task as not done yet: +T | 0 | read +---------------------------------------------------------- +Here are your tasks: +1. T | 0 | read +2. D | 1 | assignment | by 20 Jul 2024 02:15 pm +3. E | 0 | presentation | from Thurs 10am to 12pm +---------------------------------------------------------- +All done! Task added to list: + T | 0 | cs2113 assignment +The list has 4 tasks now. +---------------------------------------------------------- +Tasks matched in your list: +1. D | 1 | assignment | by 20 Jul 2024 02:15 pm +2. T | 0 | cs2113 assignment +---------------------------------------------------------- +Tasks matched in your list: +1. E | 0 | presentation | from Thurs 10am to 12pm +---------------------------------------------------------- +Okie! Task is removed from list: + T | 0 | read +The list has 3 tasks now. +---------------------------------------------------------- +Okie! Task is removed from list: + T | 0 | cs2113 assignment +The list has 2 tasks now. +---------------------------------------------------------- +Okie! Task is removed from list: + E | 0 | presentation | from Thurs 10am to 12pm +The list has 1 task now. +---------------------------------------------------------- +Okie! Task is removed from list: + D | 1 | assignment | by 20 Jul 2024 02:15 pm +The list has 0 task now. +---------------------------------------------------------- +The list is empty! +---------------------------------------------------------- +Oopsies, please add a description of Todo. +---------------------------------------------------------- +Oopsies, please add a description of event. +---------------------------------------------------------- +Oopsies, please add a description of deadline. +---------------------------------------------------------- +Oh dear, your deadline command is a bit off. +Please use: deadline [description] /by [deadline]. +---------------------------------------------------------- +Oh no! Your event command needs a bit of tweaking. +Try: event [description] /from [start time] /to [end time]. +---------------------------------------------------------- +Oh no! Your event command needs a bit of tweaking. +Try: event [description] /from [start time] /to [end time]. +---------------------------------------------------------- +Do you mean 'list'? +---------------------------------------------------------- +Please provide task number to unmark. +---------------------------------------------------------- +Please provide task number to mark. +---------------------------------------------------------- +Please provide task number to delete. +---------------------------------------------------------- +Sorry, I'm not smart enough to understand what you mean. +---------------------------------------------------------- +Goodbye. See you next time! +---------------------------------------------------------- diff --git a/text-ui-test/data/conglo.txt b/text-ui-test/data/conglo.txt new file mode 100644 index 000000000..e69de29bb diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb..61591e377 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,28 @@ +todo read +deadline assignment /by 20-07-2024 1415 +mark 1 +event presentation /from Thurs 10am /to 12pm +list +mark 2 +unmark 1 +list +todo cs2113 assignment +find assignment +find presentation +delete 1 +delete 3 +delete 2 +delete 1 +list +todo +event +deadline +deadline cook +event career fair +event exam /from 7 oct +list all +unmark +mark +delete +haha +bye diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 087374464..893146605 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -7,7 +7,7 @@ REM delete output from previous run if exist ACTUAL.TXT del ACTUAL.TXT REM compile the code into the bin folder -javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\*.java +javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\conglo\Conglo.java IF ERRORLEVEL 1 ( echo ********** BUILD FAILURE ********** exit /b 1 @@ -15,7 +15,7 @@ IF ERRORLEVEL 1 ( REM no error here, errorlevel == 0 REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT -java -classpath ..\bin Duke < input.txt > ACTUAL.TXT +java -classpath ..\bin conglo.Conglo < input.txt > ACTUAL.TXT REM compare the output to the expected output FC ACTUAL.TXT EXPECTED.TXT