diff --git a/data/oongaliegabangalie.txt b/data/oongaliegabangalie.txt new file mode 100644 index 000000000..e69de29bb diff --git a/docs/README.md b/docs/README.md index 47b9f984f..10318bb55 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,30 +1,272 @@ -# Duke User Guide +# OongaliegaBangalie Bot User Guide -// Update the title above to match the actual product name +Welcome to OongaliegaBangalie Bot! Your friendly (albeit slightly sassy) task management assistant. This guide will help you get started and make the most of this bot's features. -// Product screenshot goes here +## Table of Contents +- [Introduction](#introduction) +- [Getting Started](#getting-started) +- [Features](#features) + - [Adding Tasks](#adding-tasks) + - [Todo Tasks](#todo-tasks) + - [Deadline Tasks](#deadline-tasks) + - [Event Tasks](#event-tasks) + - [Managing Tasks](#managing-tasks) + - [Listing Tasks](#listing-tasks) + - [Marking Tasks as Done](#marking-tasks-as-done) + - [Unmarking Tasks](#unmarking-tasks) + - [Deleting Tasks](#deleting-tasks) + - [Finding Tasks](#finding-tasks) + - [Finding by Keyword](#finding-by-keyword) + - [Finding by Date](#finding-by-date) +- [Command Summary](#command-summary) +- [Error Messages](#error-messages) -// Product intro goes here +## Introduction -## Adding deadlines +OongaliegaBangalie Bot is a text-based task management application that allows you to keep track of your todos, deadlines, and events. The bot automatically saves your tasks, so you can close and reopen the application without losing your data. -// Describe the action and its outcome. +## Getting Started -// Give examples of usage +1. Ensure you have Java 11 or higher installed on your computer +2. Download the latest version of OongaliegaBangalie Bot jar file +3. Run the bot using: + ``` + java -jar oongaliegabangalie.jar + ``` +4. Start interacting with the bot using commands described in this guide -Example: `keyword (optional arguments)` +## Features -// A description of the expected outcome goes here +### Adding Tasks +OongaliegaBangalie Bot supports three types of tasks: + +#### Todo Tasks + +Todos are simple tasks with no specific timing attached. + +**Format**: `todo DESCRIPTION` + +**Example**: +``` +todo buy groceries +``` + +**Expected Output**: +``` +____________________________________________________________ +Got it. I've added this task: +[T] [ ] buy groceries +Now you have 1 tasks in the list. +____________________________________________________________ +``` + +#### Deadline Tasks + +Deadlines are tasks that need to be completed by a specific time. + +**Format**: `deadline DESCRIPTION /by DATE_TIME` + +The date time can be entered in these formats: +- `yyyy-MM-dd HH:mm` (e.g., 2023-03-15 14:30) +- `d/M/yyyy HHmm` (e.g., 15/3/2023 1430) + +**Example**: +``` +deadline submit assignment /by 2023-04-01 23:59 +``` + +**Expected Output**: +``` +____________________________________________________________ +Got it. I've added this task: +[D] [ ] submit assignment (by: Apr 1 2023, 11:59 PM) +Now you have 2 tasks in the list. +____________________________________________________________ +``` + +#### Event Tasks + +Events are tasks that take place from one time to another. + +**Format**: `event DESCRIPTION /from DATE_TIME /to DATE_TIME` + +**Example**: +``` +event team meeting /from 2023-04-02 10:00 /to 2023-04-02 12:00 +``` + +**Expected Output**: +``` +____________________________________________________________ +Got it. I've added this task: +[E] [ ] team meeting (from: Apr 2 2023, 10:00 AM to: Apr 2 2023, 12:00 PM) +Now you have 3 tasks in the list. +____________________________________________________________ +``` + +### Managing Tasks + +#### Listing Tasks + +To see all your tasks, use the list command. + +**Format**: `list` + +**Example**: +``` +list +``` + +**Expected Output**: +``` +____________________________________________________________ +Here are the tasks in your list: +1. [T] [ ] buy groceries +2. [D] [ ] submit assignment (by: Apr 1 2023, 11:59 PM) +3. [E] [ ] team meeting (from: Apr 2 2023, 10:00 AM to: Apr 2 2023, 12:00 PM) +Better get to it quick! +____________________________________________________________ +``` + +#### Marking Tasks as Done + +To mark a task as completed, use the mark command with the task number. + +**Format**: `mark TASK_NUMBER` + +**Example**: +``` +mark 1 ``` -expected output + +**Expected Output**: +``` +____________________________________________________________ +Nice! I've marked this task as done: + [T] [X] buy groceries +Now go do something else and stop bothering me! +____________________________________________________________ +``` + +#### Unmarking Tasks + +If you need to change a task back to not done, use the unmark command. + +**Format**: `unmark TASK_NUMBER` + +**Example**: +``` +unmark 1 +``` + +**Expected Output**: +``` +____________________________________________________________ +OK, I've marked this task as not done yet: + [T] [ ] buy groceries +You better get to it... +____________________________________________________________ ``` -## Feature ABC +#### Deleting Tasks + +To remove a task from your list, use the delete command. + +**Format**: `delete TASK_NUMBER` + +**Example**: +``` +delete 3 +``` + +**Expected Output**: +``` +____________________________________________________________ +Noted. I've removed this task: +[E] [ ] team meeting (from: Apr 2 2023, 10:00 AM to: Apr 2 2023, 12:00 PM) +Now you have 2 tasks in the list +____________________________________________________________ +``` + +### Finding Tasks + +#### Finding by Keyword + +To find tasks containing a specific keyword, use the find command. + +**Format**: `find KEYWORD` + +**Example**: +``` +find assignment +``` + +**Expected Output**: +``` +____________________________________________________________ +Here are the matching tasks in your list: +1.[D] [ ] submit assignment (by: Apr 1 2023, 11:59 PM) +____________________________________________________________ +``` + +#### Finding by Date + +To find tasks occurring on a specific date, use the finddate command. + +**Format**: `finddate YYYY-MM-DD` + +**Example**: +``` +finddate 2023-04-01 +``` + +**Expected Output**: +``` +____________________________________________________________ +Here are the tasks on 2023-04-01: +1. [D] [ ] submit assignment (by: Apr 1 2023, 11:59 PM) +Not too busy, but you should still get on with it! +____________________________________________________________ +``` + +## Command Summary + +| Command | Format | Example | +|---------|--------|---------| +| Add a todo | `todo DESCRIPTION` | `todo buy groceries` | +| Add a deadline | `deadline DESCRIPTION /by DATE_TIME` | `deadline submit assignment /by 2023-04-01 23:59` | +| Add an event | `event DESCRIPTION /from DATE_TIME /to DATE_TIME` | `event team meeting /from 2023-04-02 10:00 /to 2023-04-02 12:00` | +| List tasks | `list` | `list` | +| Mark as done | `mark TASK_NUMBER` | `mark 1` | +| Mark as not done | `unmark TASK_NUMBER` | `unmark 1` | +| Delete a task | `delete TASK_NUMBER` | `delete 1` | +| Find by keyword | `find KEYWORD` | `find assignment` | +| Find by date | `finddate YYYY-MM-DD` | `finddate 2023-04-01` | +| Exit | `bye` | `bye` | + +## Error Messages + +OongaliegaBangalie Bot will provide helpful (though sometimes sassy) error messages when commands are incorrectly formatted: + +- Todo without description: + ``` + stop wasting my time and add the description of the task after the command... + ``` -// Feature details +- Deadline without description: + ``` + haha very funny... why is there nothing after the command? + ``` +- Invalid task number: + ``` + Task #5 doesn't exist! Check your list again (or your head)! + ``` -## Feature XYZ +- Empty task list: + ``` + Your list is empty! nothing to see here... + ``` -// Feature details \ No newline at end of file +The bot will automatically save your tasks between sessions, so you don't have to worry about losing your data. \ No newline at end of file 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..d11e2615a --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: oongaliegabangalieBot.ui.oongaliegabangalie + diff --git a/src/main/java/oongaliegabangalieBot/TaskList.java b/src/main/java/oongaliegabangalieBot/TaskList.java new file mode 100644 index 000000000..5a01de257 --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/TaskList.java @@ -0,0 +1,144 @@ +package oongaliegabangalieBot; + +import java.util.ArrayList; + +import oongaliegabangalieBot.exception.botException; +import oongaliegabangalieBot.task.Task; + +/** + * Manages the list of tasks. + * Provides methods to add, delete, and manipulate tasks. + */ +public class TaskList { + private ArrayList tasks; + + /** + * Constructs an empty task list + */ + public TaskList() { + tasks = new ArrayList<>(); + } + + /** + * Constructs a task list with existing tasks + */ + public TaskList(ArrayList tasks) { + this.tasks = tasks != null ? tasks : new ArrayList<>(); + } + + /** + * Adds a task to the list + */ + public void addTask(Task task) { + tasks.add(task); + } + + /** + * Deletes a task from the list + * @param index Task index (1-based) + * @return The deleted task + * @throws botException if index is invalid + */ + public Task deleteTask(int index) throws botException { + // Check if list is empty + if (tasks.isEmpty()) { + throw new botException("Your list is empty man... nothing to delete"); + } + + int taskIndex = index - 1; // Convert to 0-based index + + // Check if taskIndex is valid + if (taskIndex < 0 || taskIndex >= tasks.size()) { + throw new botException("Task #" + index + " doesn't exist! Check your list again (or your head)!"); + } + + // Get task and remove it + Task taskToDelete = tasks.get(taskIndex); + tasks.remove(taskIndex); + + return taskToDelete; + } + + /** + * Marks a task as done + * @param index Task index (1-based) + * @return The marked task + * @throws botException if index is invalid + */ + public Task markTaskAsDone(int index) throws botException { + int taskIndex = index - 1; // Convert to 0-based index + + // Check if taskIndex is valid + if (taskIndex < 0 || taskIndex >= tasks.size()) { + throw new botException("Task #" + index + " doesn't exist! Check your list again (or your head)!"); + } + + // Check if task is already done + Task task = tasks.get(taskIndex); + if (task.getIsDone()) { + throw new botException("Task #" + index + " is already marked as done! Don't worry I know you did it already!"); + } + + // Mark as done + task.markAsDone(); + return task; + } + + /** + * Marks a task as not done + * @param index Task index (1-based) + * @return The unmarked task + * @throws botException if index is invalid + */ + public Task markTaskAsNotDone(int index) throws botException { + int taskIndex = index - 1; // Convert to 0-based index + + // Check if taskIndex is valid + if (taskIndex < 0 || taskIndex >= tasks.size()) { + throw new botException("Task #" + index + " doesn't exist! Check your list again (or your head)!"); + } + + // Check if task is already not done + Task task = tasks.get(taskIndex); + if (!task.getIsDone()) { + throw new botException("Task #" + index + " is already marked as not done! You think I don't do my job properly?"); + } + + // Mark as not done + task.markAsNotDone(); + return task; + } + + /** + * Gets all tasks in the list + * @return ArrayList of tasks + * @throws botException if list is empty + */ + public ArrayList getAllTasks() throws botException { + if (tasks.isEmpty()) { + throw new botException("Your list is empty! nothing to see here..."); + } + return tasks; + } + + /** + * Gets the size of the task list + */ + public int size() { + return tasks.size(); + } + + /** + * Gets the tasks as an ArrayList (for storage) + */ + public ArrayList getTasksArray() { + return tasks; + } + + /** + * Checks if the task list is empty + */ + public boolean isEmpty() { + return tasks.isEmpty(); + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/commands/Command.java b/src/main/java/oongaliegabangalieBot/commands/Command.java new file mode 100644 index 000000000..a1fec3e60 --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/commands/Command.java @@ -0,0 +1,41 @@ +package oongaliegabangalieBot.commands; + +import oongaliegabangalieBot.TaskList; +import oongaliegabangalieBot.exception.botException; +import oongaliegabangalieBot.storage.Storage; +import oongaliegabangalieBot.ui.Ui; + +/** + * Abstract class for all commands. + */ +public abstract class Command { + /** + * Executes the command. + * + * @param tasks The task list + * @param ui The UI + * @param storage The storage + * @throws botException If there's an error executing the command + */ + public abstract void execute(TaskList tasks, Ui ui, Storage storage) throws botException; + + /** + * Indicates if this command is the exit command. + * + * @return true if this is an exit command, false otherwise + */ + public boolean isExit() { + return false; + } + + /** + * Helper method to save tasks to storage + */ + protected void saveTasksToStorage(TaskList tasks, Storage storage) { + try { + storage.saveTasks(tasks.getTasksArray()); + } catch (botException e) { + System.out.println("Warning: Failed to save tasks: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/commands/DeadlineCommand.java b/src/main/java/oongaliegabangalieBot/commands/DeadlineCommand.java new file mode 100644 index 000000000..a917a13a8 --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/commands/DeadlineCommand.java @@ -0,0 +1,28 @@ +package oongaliegabangalieBot.commands; + +import oongaliegabangalieBot.TaskList; +import oongaliegabangalieBot.exception.botException; +import oongaliegabangalieBot.storage.Storage; +import oongaliegabangalieBot.task.Deadline; +import oongaliegabangalieBot.ui.Ui; + +/** + * Represents a command to add a deadline task. + */ +public class DeadlineCommand extends Command { + private final String description; + private final String by; + + public DeadlineCommand(String description, String by) { + this.description = description; + this.by = by; + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws botException { + Deadline deadline = new Deadline(description, by); + tasks.addTask(deadline); + saveTasksToStorage(tasks, storage); + ui.showAddedTask(deadline, tasks.size()); + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/commands/DeleteCommand.java b/src/main/java/oongaliegabangalieBot/commands/DeleteCommand.java new file mode 100644 index 000000000..5aa5bbe29 --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/commands/DeleteCommand.java @@ -0,0 +1,25 @@ +package oongaliegabangalieBot.commands; + +import oongaliegabangalieBot.TaskList; +import oongaliegabangalieBot.exception.botException; +import oongaliegabangalieBot.storage.Storage; +import oongaliegabangalieBot.task.Task; +import oongaliegabangalieBot.ui.Ui; + +/** + * Represents a command to delete a task. + */ +public class DeleteCommand extends Command { + private final int taskNumber; + + public DeleteCommand(int taskNumber) { + this.taskNumber = taskNumber; + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws botException { + Task deletedTask = tasks.deleteTask(taskNumber); + saveTasksToStorage(tasks, storage); + ui.showDeletedTask(deletedTask, tasks.size()); + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/commands/EventCommand.java b/src/main/java/oongaliegabangalieBot/commands/EventCommand.java new file mode 100644 index 000000000..351cdde1c --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/commands/EventCommand.java @@ -0,0 +1,30 @@ +package oongaliegabangalieBot.commands; + +import oongaliegabangalieBot.TaskList; +import oongaliegabangalieBot.exception.botException; +import oongaliegabangalieBot.storage.Storage; +import oongaliegabangalieBot.task.Event; +import oongaliegabangalieBot.ui.Ui; + +/** + * Represents a command to add an event task. + */ +public class EventCommand extends Command { + private final String description; + private final String from; + private final String to; + + public EventCommand(String description, String from, String to) { + this.description = description; + this.from = from; + this.to = to; + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws botException { + Event event = new Event(description, from, to); + tasks.addTask(event); + saveTasksToStorage(tasks, storage); + ui.showAddedTask(event, tasks.size()); + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/commands/ExitCommand.java b/src/main/java/oongaliegabangalieBot/commands/ExitCommand.java new file mode 100644 index 000000000..91b5c7c79 --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/commands/ExitCommand.java @@ -0,0 +1,21 @@ +package oongaliegabangalieBot.commands; + +import oongaliegabangalieBot.TaskList; +import oongaliegabangalieBot.storage.Storage; +import oongaliegabangalieBot.ui.Ui; + +/** + * Represents a command to exit the application. + */ +public class ExitCommand extends Command { + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) { + ui.showGoodbye(); + } + + @Override + public boolean isExit() { + return true; + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/commands/FindCommand.java b/src/main/java/oongaliegabangalieBot/commands/FindCommand.java new file mode 100644 index 000000000..3d51d69fa --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/commands/FindCommand.java @@ -0,0 +1,45 @@ +package oongaliegabangalieBot.commands; + +import oongaliegabangalieBot.TaskList; +import oongaliegabangalieBot.exception.botException; +import oongaliegabangalieBot.storage.Storage; +import oongaliegabangalieBot.task.Task; +import oongaliegabangalieBot.ui.Ui; + +import java.util.ArrayList; + +/** + * Represents a command to find tasks by keyword. + */ +public class FindCommand extends Command { + private final String keyword; + + public FindCommand(String keyword) { + this.keyword = keyword; + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws botException { + ArrayList allTasks; + + try { + allTasks = tasks.getAllTasks(); + } catch (botException e) { + // If no tasks, show appropriate message + ui.showError(new botException("Cannot find tasks when your list is empty! Nothing to search through...")); + return; + } + + ArrayList matchingTasks = new ArrayList<>(); + + // Find all tasks that contain the keyword (case-insensitive) + for (Task task : allTasks) { + if (task.getDescription().toLowerCase().contains(keyword.toLowerCase())) { + matchingTasks.add(task); + } + } + + // Show the found tasks + ui.showMatchingTasks(matchingTasks, keyword); + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/commands/FindDateCommand.java b/src/main/java/oongaliegabangalieBot/commands/FindDateCommand.java new file mode 100644 index 000000000..8144877cc --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/commands/FindDateCommand.java @@ -0,0 +1,75 @@ +package oongaliegabangalieBot.commands; + +import oongaliegabangalieBot.TaskList; +import oongaliegabangalieBot.exception.botException; +import oongaliegabangalieBot.storage.Storage; +import oongaliegabangalieBot.task.Deadline; +import oongaliegabangalieBot.task.Event; +import oongaliegabangalieBot.task.Task; +import oongaliegabangalieBot.ui.Ui; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; + +/** + * Represents a command to find tasks by date. + */ +public class FindDateCommand extends Command { + private final String dateString; + private final LocalDate targetDate; + + public FindDateCommand(String dateString) { + this.dateString = dateString; + // Parse the date string into a LocalDate object + this.targetDate = LocalDate.parse(dateString, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws botException { + ArrayList allTasks = tasks.getAllTasks(); + ArrayList tasksOnDate = new ArrayList<>(); + + // Find all tasks that occur on the target date + for (Task task : allTasks) { + if (task instanceof Deadline) { + Deadline deadline = (Deadline) task; + LocalDateTime deadlineDateTime = deadline.getByDateTime(); + + // Skip if deadline doesn't have a valid date + if (deadlineDateTime == null) { + continue; + } + + // Check if the deadline date matches the target date + if (deadlineDateTime.toLocalDate().equals(targetDate)) { + tasksOnDate.add(task); + } + } else if (task instanceof Event) { + Event event = (Event) task; + LocalDateTime fromDateTime = event.getFromDateTime(); + LocalDateTime toDateTime = event.getToDateTime(); + + // Skip if event doesn't have valid dates + if (fromDateTime == null || toDateTime == null) { + continue; + } + + // Check if the event starts or ends on the target date + // Or if the event spans over the target date + LocalDate fromDate = fromDateTime.toLocalDate(); + LocalDate toDate = toDateTime.toLocalDate(); + + if (fromDate.equals(targetDate) || + toDate.equals(targetDate) || + (targetDate.isAfter(fromDate) && targetDate.isBefore(toDate))) { + tasksOnDate.add(task); + } + } + } + + // Show the found tasks + ui.showTasksOnDate(tasksOnDate, dateString); + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/commands/ListCommand.java b/src/main/java/oongaliegabangalieBot/commands/ListCommand.java new file mode 100644 index 000000000..42bd258a5 --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/commands/ListCommand.java @@ -0,0 +1,21 @@ +package oongaliegabangalieBot.commands; + +import java.util.ArrayList; + +import oongaliegabangalieBot.TaskList; +import oongaliegabangalieBot.exception.botException; +import oongaliegabangalieBot.storage.Storage; +import oongaliegabangalieBot.task.Task; +import oongaliegabangalieBot.ui.Ui; + +/** + * Represents a command to list all tasks. + */ +public class ListCommand extends Command { + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws botException { + ArrayList taskList = tasks.getAllTasks(); + ui.showTaskList(taskList); + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/commands/MarkCommand.java b/src/main/java/oongaliegabangalieBot/commands/MarkCommand.java new file mode 100644 index 000000000..50ae06ac3 --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/commands/MarkCommand.java @@ -0,0 +1,25 @@ +package oongaliegabangalieBot.commands; + +import oongaliegabangalieBot.TaskList; +import oongaliegabangalieBot.exception.botException; +import oongaliegabangalieBot.storage.Storage; +import oongaliegabangalieBot.task.Task; +import oongaliegabangalieBot.ui.Ui; + +/** + * Represents a command to mark a task as done. + */ +public class MarkCommand extends Command { + private final int taskNumber; + + public MarkCommand(int taskNumber) { + this.taskNumber = taskNumber; + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws botException { + Task markedTask = tasks.markTaskAsDone(taskNumber); + saveTasksToStorage(tasks, storage); + ui.showMarkedDoneTask(markedTask); + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/commands/TodoCommand.java b/src/main/java/oongaliegabangalieBot/commands/TodoCommand.java new file mode 100644 index 000000000..20ddfb923 --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/commands/TodoCommand.java @@ -0,0 +1,26 @@ +package oongaliegabangalieBot.commands; + +import oongaliegabangalieBot.TaskList; +import oongaliegabangalieBot.exception.botException; +import oongaliegabangalieBot.storage.Storage; +import oongaliegabangalieBot.task.Todo; +import oongaliegabangalieBot.ui.Ui; + +/** + * Represents a command to add a todo task. + */ +public class TodoCommand extends Command { + private final String description; + + public TodoCommand(String description) { + this.description = description; + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws botException { + Todo todo = new Todo(description); + tasks.addTask(todo); + saveTasksToStorage(tasks, storage); + ui.showAddedTask(todo, tasks.size()); + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/commands/UnmarkCommand.java b/src/main/java/oongaliegabangalieBot/commands/UnmarkCommand.java new file mode 100644 index 000000000..3696b702c --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/commands/UnmarkCommand.java @@ -0,0 +1,25 @@ +package oongaliegabangalieBot.commands; + +import oongaliegabangalieBot.TaskList; +import oongaliegabangalieBot.exception.botException; +import oongaliegabangalieBot.storage.Storage; +import oongaliegabangalieBot.task.Task; +import oongaliegabangalieBot.ui.Ui; + +/** + * Represents a command to mark a task as not done. + */ +public class UnmarkCommand extends Command { + private final int taskNumber; + + public UnmarkCommand(int taskNumber) { + this.taskNumber = taskNumber; + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws botException { + Task unmarkedTask = tasks.markTaskAsNotDone(taskNumber); + saveTasksToStorage(tasks, storage); + ui.showMarkedNotDoneTask(unmarkedTask); + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/exception/botException.java b/src/main/java/oongaliegabangalieBot/exception/botException.java new file mode 100644 index 000000000..065a6dcdd --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/exception/botException.java @@ -0,0 +1,7 @@ +package oongaliegabangalieBot.exception; + +public class botException extends Exception { + public botException(String errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/oongaliegabangalieBot/oongaliegabangalie.java b/src/main/java/oongaliegabangalieBot/oongaliegabangalie.java new file mode 100644 index 000000000..0ebe5baf4 --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/oongaliegabangalie.java @@ -0,0 +1,87 @@ +package oongaliegabangalieBot; + +import java.io.File; +import java.util.ArrayList; + +import oongaliegabangalieBot.commands.Command; +import oongaliegabangalieBot.commands.ExitCommand; +import oongaliegabangalieBot.exception.botException; +import oongaliegabangalieBot.parser.Parser; +import oongaliegabangalieBot.storage.Storage; +import oongaliegabangalieBot.task.Task; +import oongaliegabangalieBot.ui.Ui; + +/** + * Main class for Oongaliegabangalie Bot + * Coordinates between the UI, TaskList, Storage, and Parser components + */ +public class oongaliegabangalie { + // Storage filepath + private static final String STORAGE_DIRECTORY = "data"; + private static final String STORAGE_FILENAME = "oongaliegabangalie.txt"; + private static final String STORAGE_PATH = STORAGE_DIRECTORY + File.separator + STORAGE_FILENAME; + + // Instance Fields + private Ui ui; + private TaskList tasks; + private Storage storage; + private Parser parser; + + public oongaliegabangalie() { + this(STORAGE_PATH); + } + + public oongaliegabangalie(String filePath) { + ui = new Ui(); + storage = new Storage(filePath); + parser = new Parser(); + try { + ArrayList loadedTasks = storage.loadTasks(); + tasks = new TaskList(loadedTasks); + if (!loadedTasks.isEmpty()) { + ui.showLoadedTasksMessage(loadedTasks.size()); + } + } catch (botException e) { + ui.showLoadingError(e.getMessage()); + tasks = new TaskList(); + } + } + + /** + * Core program logic + * Ties together other classes and functions + */ + public void run() { + ui.showWelcome(); + + boolean isExit = false; + + while (!isExit) { + try { + String userInput = ui.readCommand(); + try { + // Parse the command + Command command = parser.parseCommand(userInput); + + // Execute the command + command.execute(tasks, ui, storage); + + // Check if exit command + isExit = command.isExit(); + if (isExit) { + (new ExitCommand()).execute(tasks, ui, storage); + } + + } catch (botException e) { + ui.showError(e); + } + } catch (Exception e) { + ui.showError(new botException("Unexpected error: " + e.getMessage())); + } + } + } + + public static void main(String[] args) { + new oongaliegabangalie().run(); + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/parser/Parser.java b/src/main/java/oongaliegabangalieBot/parser/Parser.java new file mode 100644 index 000000000..12cab7437 --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/parser/Parser.java @@ -0,0 +1,261 @@ +package oongaliegabangalieBot.parser; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +import oongaliegabangalieBot.commands.*; +import oongaliegabangalieBot.exception.botException; + +/** + * Parser handles the parsing of user commands. + */ +public class Parser { + // Command keywords + private static final String TODO_COMMAND = "todo"; + private static final String DEADLINE_COMMAND = "deadline"; + private static final String EVENT_COMMAND = "event"; + private static final String DELETE_COMMAND = "delete"; + private static final String LIST_COMMAND = "list"; + private static final String MARK_COMMAND = "mark"; + private static final String UNMARK_COMMAND = "unmark"; + private static final String FIND_COMMAND = "find"; + private static final String FIND_DATE_COMMAND = "finddate"; // New command for finding by date + + /** + * Parses a command string and returns a Command object + * @param input The user's input string + * @return A Command object + * @throws botException if the command is invalid + */ + public Command parseCommand(String input) throws botException { + input = input.trim(); + + // Check for blank input + if (input.isBlank()) { + throw new botException("hello say something pls"); + } + + // Parse different command types + if (input.equalsIgnoreCase("bye")) { + return new ExitCommand(); + } else if (input.startsWith(TODO_COMMAND)) { + return parseTodoCommand(input); + } else if (input.startsWith(DEADLINE_COMMAND)) { + return parseDeadlineCommand(input); + } else if (input.startsWith(EVENT_COMMAND)) { + return parseEventCommand(input); + } else if (input.startsWith(DELETE_COMMAND)) { + return parseDeleteCommand(input); + } else if (input.equalsIgnoreCase(LIST_COMMAND)) { + return new ListCommand(); + } else if (input.startsWith(MARK_COMMAND)) { + return parseMarkCommand(input); + } else if (input.startsWith(UNMARK_COMMAND)) { + return parseUnmarkCommand(input); + } else if (input.startsWith(FIND_COMMAND)) { + return parseFindCommand(input); + } else if (input.startsWith(FIND_DATE_COMMAND)) { + return parseFindDateCommand(input); + } else { + throw new botException("I don't know what that means"); + } + } + + /** + * Parses a todo command + */ + private Command parseTodoCommand(String input) throws botException { + String description = input.length() > TODO_COMMAND.length() ? + input.substring(TODO_COMMAND.length()).trim() : ""; + + if (description.isEmpty()) { + throw new botException("stop wasting my time and add the description of the task after the command..."); + } + + return new TodoCommand(description); + } + + /** + * Parses a deadline command + */ + private Command parseDeadlineCommand(String input) throws botException { + String content = input.length() > DEADLINE_COMMAND.length() ? + input.substring(DEADLINE_COMMAND.length()).trim() : ""; + + if (content.isEmpty()) { + throw new botException("haha very funny... why is there nothing after the command?"); + } + + String[] parts = parseDeadlineParts(content); + return new DeadlineCommand(parts[0], parts[1]); + } + + /** + * Parses the parts of a deadline command + */ + private String[] parseDeadlineParts(String content) throws botException { + final String DEADLINE_MARKER = "/by"; + + int byIndex = content.indexOf(DEADLINE_MARKER); + if (byIndex == -1) { + throw new botException("Wheres the '/by' marker? I need that please"); + } + + String description = content.substring(0, byIndex).trim(); + if (description.isEmpty()) { + throw new botException("Where is the description of the task? please add it before the deadline!"); + } + + String by = content.substring(byIndex + DEADLINE_MARKER.length()).trim(); + if (by.isEmpty()) { + throw new botException("Why is there nothing after '/by'? do you not want to finish on time?"); + } + + return new String[]{description, by}; + } + + /** + * Parses a find date command + */ + private Command parseFindDateCommand(String input) throws botException { + String dateStr = input.length() > FIND_DATE_COMMAND.length() ? + input.substring(FIND_DATE_COMMAND.length()).trim() : ""; + + if (dateStr.isEmpty()) { + throw new botException("Please specify a date after 'finddate' (e.g., finddate 2019-12-02)"); + } + + try { + // Try to parse the date to validate it + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDateTime.parse(dateStr + " 00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); + return new FindDateCommand(dateStr); + } catch (DateTimeParseException e) { + throw new botException("Invalid date format. Please use yyyy-MM-dd format (e.g., 2019-12-02)"); + } + } + + /** + * Parses an event command + */ + private Command parseEventCommand(String input) throws botException { + String content = input.length() > EVENT_COMMAND.length() ? + input.substring(EVENT_COMMAND.length()).trim() : ""; + + if (content.isEmpty()) { + throw new botException("Why is there nothing after the command? are you playing with me?"); + } + + String[] parts = parseEventParts(content); + return new EventCommand(parts[0], parts[1], parts[2]); + } + + /** + * Parses the parts of an event command + */ + private String[] parseEventParts(String content) throws botException { + final String EVENT_FROM_MARKER = "/from"; + final String EVENT_TO_MARKER = "/to"; + + int fromIndex = content.indexOf(EVENT_FROM_MARKER); + int toIndex = content.indexOf(EVENT_TO_MARKER); + + if (fromIndex == -1) { + throw new botException("Wheres the '/from' marker? I need that please"); + } + + if (toIndex == -1) { + throw new botException("Wheres the '/to' marker? I need that please"); + } + + if (toIndex < fromIndex) { + throw new botException("I think you got it mixed up! it should be '/from' then '/to'"); + } + + String description = content.substring(0, fromIndex).trim(); + if (description.isBlank()) { + throw new botException("Where is the description of the event? please add it before the timings!"); + } + + String from = content.substring(fromIndex + EVENT_FROM_MARKER.length(), toIndex).trim(); + if (from.isEmpty()) { + throw new botException("Why is there nothing after '/from'? can you pls follow instructions!"); + } + + String to = content.substring(toIndex + EVENT_TO_MARKER.length()).trim(); + if (to.isEmpty()) { + throw new botException("Why is there nothing after '/to'? can you pls follow instructions!"); + } + + return new String[]{description, from, to}; + } + + /** + * Parses a delete command + */ + private Command parseDeleteCommand(String input) throws botException { + String taskNumberStr = input.substring(DELETE_COMMAND.length()).trim(); + + if (taskNumberStr.isEmpty()) { + throw new botException("Which task do you want me to delete? provide a task number after 'delete'"); + } + + try { + int taskNumber = Integer.parseInt(taskNumberStr); + return new DeleteCommand(taskNumber); + } catch (NumberFormatException e) { + throw new botException("'" + taskNumberStr + "' isn't a task number bro"); + } + } + + /** + * Parses a mark command + */ + private Command parseMarkCommand(String input) throws botException { + String taskNumberStr = input.substring(MARK_COMMAND.length()).trim(); + + if (taskNumberStr.isEmpty()) { + throw new botException("How am I supposed to know which task to mark? Can you pLease provide a task number after 'mark'"); + } + + try { + int taskNumber = Integer.parseInt(taskNumberStr); + return new MarkCommand(taskNumber); + } catch (NumberFormatException e) { + throw new botException("'" + taskNumberStr + "' isn't a task number bro"); + } + } + + /** + * Parses an unmark command + */ + private Command parseUnmarkCommand(String input) throws botException { + String taskNumberStr = input.substring(UNMARK_COMMAND.length()).trim(); + + if (taskNumberStr.isEmpty()) { + throw new botException("How am I supposed to know which task to unmark? Can you please provide a task number after 'unmark'"); + } + + try { + int taskNumber = Integer.parseInt(taskNumberStr); + return new UnmarkCommand(taskNumber); + } catch (NumberFormatException e) { + throw new botException("'" + taskNumberStr + "' isn't a task number bro"); + } + } + + /** + * Parses a find command + */ + private Command parseFindCommand(String input) throws botException { + String keyword = input.length() > FIND_COMMAND.length() ? + input.substring(FIND_COMMAND.length()).trim() : ""; + + if (keyword.isEmpty()) { + throw new botException("How am I supposed to find anything when you don't tell me what to look for? Please provide a keyword after 'find'"); + } + + return new FindCommand(keyword); + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/storage/Storage.java b/src/main/java/oongaliegabangalieBot/storage/Storage.java new file mode 100644 index 000000000..a68a83501 --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/storage/Storage.java @@ -0,0 +1,157 @@ +package oongaliegabangalieBot.storage; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Scanner; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +import oongaliegabangalieBot.exception.botException; +import oongaliegabangalieBot.task.Deadline; +import oongaliegabangalieBot.task.Event; +import oongaliegabangalieBot.task.Task; +import oongaliegabangalieBot.task.Todo; + +public class Storage { + private final String filePath; + private final String directoryPath; + private static final DateTimeFormatter STORAGE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + + // Constructor initializes the file path + public Storage(String filePath) { + this.filePath = filePath; + // Extract directory path from the file path + int lastSeparatorIndex = filePath.lastIndexOf(File.separator); + this.directoryPath = lastSeparatorIndex > 0 ? filePath.substring(0, lastSeparatorIndex) : "."; + } + + /* + Saves the tasks to the file + Format: T | isDone | description + D | isDone | description | by + E | isDone | description | from | to + */ + public void saveTasks(ArrayList tasks) throws botException { + try { + // Create directory if it doesn't exist + File directory = new File(directoryPath); + if (!directory.exists()) { + directory.mkdirs(); + } + + // Create file writer (will create file if it doesn't exist) + FileWriter writer = new FileWriter(filePath); + + // Write each task to the file + for (Task task : tasks) { + if (task instanceof Todo) { + writer.write("T | " + (task.getIsDone() ? "1" : "0") + " | " + task.getDescription()); + } else if (task instanceof Deadline) { + Deadline deadline = (Deadline) task; + String byForStorage = deadline.getByForStorage(); // Get the standardized date format + writer.write("D | " + (task.getIsDone() ? "1" : "0") + " | " + + task.getDescription() + " | " + byForStorage); + } else if (task instanceof Event) { + Event event = (Event) task; + String fromForStorage = event.getFromForStorage(); + String toForStorage = event.getToForStorage(); + writer.write("E | " + (task.getIsDone() ? "1" : "0") + " | " + + task.getDescription() + " | " + fromForStorage + " | " + toForStorage); + } + writer.write(System.lineSeparator()); // Add newline + } + + writer.close(); // Important to close the file writer! + } catch (IOException e) { + throw new botException("Error saving tasks: " + e.getMessage()); + } + } + + // Loads tasks from the file + public ArrayList loadTasks() throws botException { + ArrayList tasks = new ArrayList<>(); + + // Check if file exists, if not return empty array + File file = new File(filePath); + if (!file.exists()) { + return tasks; + } + + try { + Scanner scanner = new Scanner(file); + + while (scanner.hasNextLine()) { + try { + String line = scanner.nextLine(); + + // Skip empty lines + if (line.trim().isEmpty()) { + continue; + } + + // Parse the line + String[] parts = line.split(" \\| ", -1); // -1 to keep empty strings + + // Validate line format + if (parts.length < 3) { + System.out.println("Warning: Skipping invalid line format: " + line); + continue; + } + + char taskType = parts[0].charAt(0); + boolean isDone = parts[1].equals("1"); + String description = parts[2]; + + Task task = null; + + switch (taskType) { + case 'T': // Todo + task = new Todo(description); + break; + case 'D': // Deadline + if (parts.length < 4) { + System.out.println("Warning: Skipping invalid Deadline format: " + line); + continue; + } + + // Try to parse the date/time + String byString = parts[3]; + task = new Deadline(description, byString); + break; + case 'E': // Event + if (parts.length < 5) { + System.out.println("Warning: Skipping invalid Event format: " + line); + continue; + } + task = new Event(description, parts[3], parts[4]); + break; + default: + System.out.println("Warning: Unknown task type: " + taskType); + continue; + } + + // Set the task status + if (isDone) { + task.markAsDone(); + } + + tasks.add(task); + } catch (Exception e) { + // Handle corrupted line, print warning and continue + System.out.println("Warning: Skipping corrupted line. Error: " + e.getMessage()); + } + } + + scanner.close(); + } catch (FileNotFoundException e) { + throw new botException("Error loading tasks: " + e.getMessage()); + } + + return tasks; + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/task/Deadline.java b/src/main/java/oongaliegabangalieBot/task/Deadline.java new file mode 100644 index 000000000..5ecd6f0de --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/task/Deadline.java @@ -0,0 +1,80 @@ +package oongaliegabangalieBot.task; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class Deadline extends Task { + + // DateTime formatter for displaying the deadline + private static final DateTimeFormatter DISPLAY_FORMATTER = DateTimeFormatter.ofPattern("MMM d yyyy, hh:mm a"); + + // DateTime formatter for parsing and storing dates + private static final DateTimeFormatter STORAGE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + // new variable - due date as LocalDateTime + protected LocalDateTime by; + + // original by string (kept for compatibility) + protected String byString; + + // constructs a new deadline with description and due date (string format) + public Deadline(String description, String by) { + super(description); + this.byString = by; + this.by = null; // Will be set if parsed successfully + try { + this.by = parseDateTime(by); + } catch (Exception e) { + // If parsing fails, keep the original string but don't set the LocalDateTime + System.out.println("Warning: Date format not recognized for: " + by); + } + } + + // Constructs a new deadline with description and LocalDateTime + public Deadline(String description, LocalDateTime by) { + super(description); + this.by = by; + this.byString = by.format(STORAGE_FORMATTER); + } + + // Try to parse various date formats + private LocalDateTime parseDateTime(String dateTimeStr) { + try { + // Try to parse using standard formats like: + // yyyy-MM-dd HH:mm + return LocalDateTime.parse(dateTimeStr, STORAGE_FORMATTER); + } catch (Exception e1) { + try { + // Try to parse formats like d/M/yyyy HHmm (e.g., 2/12/2019 1800) + DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("d/M/yyyy HHmm"); + return LocalDateTime.parse(dateTimeStr, customFormatter); + } catch (Exception e2) { + throw new IllegalArgumentException("Could not parse date: " + dateTimeStr); + } + } + } + + public String getBy() { + return byString; + } + + public LocalDateTime getByDateTime() { + return by; + } + + // For storage purposes, return the standardized date format + public String getByForStorage() { + return by != null ? by.format(STORAGE_FORMATTER) : byString; + } + + @Override + public String toString() { + if (by != null) { + // Display formatted date if available + return "[D]" + super.toString() + " (by: " + by.format(DISPLAY_FORMATTER) + ")"; + } else { + // Fallback to original string if date parsing failed + return "[D]" + super.toString() + " (by: " + byString + ")"; + } + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/task/Event.java b/src/main/java/oongaliegabangalieBot/task/Event.java new file mode 100644 index 000000000..dfaa81abb --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/task/Event.java @@ -0,0 +1,106 @@ +package oongaliegabangalieBot.task; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class Event extends Task { + + // DateTime formatter for displaying dates + private static final DateTimeFormatter DISPLAY_FORMATTER = DateTimeFormatter.ofPattern("MMM d yyyy, hh:mm a"); + + // DateTime formatter for parsing and storing dates + private static final DateTimeFormatter STORAGE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + // new variables - start and end time as LocalDateTime + protected LocalDateTime fromDateTime; + protected LocalDateTime toDateTime; + + // original string versions (kept for compatibility) + protected String from; + protected String to; + + // constructs a new event with description, start and end times as strings + public Event(String description, String from, String to) { + super(description); + this.from = from; + this.to = to; + + // Try to parse the date/time strings + try { + this.fromDateTime = parseDateTime(from); + } catch (Exception e) { + // If parsing fails, keep as null + this.fromDateTime = null; + } + + try { + this.toDateTime = parseDateTime(to); + } catch (Exception e) { + // If parsing fails, keep as null + this.toDateTime = null; + } + } + + // Constructs a new event with description and LocalDateTime objects + public Event(String description, LocalDateTime from, LocalDateTime to) { + super(description); + this.fromDateTime = from; + this.toDateTime = to; + this.from = from.format(STORAGE_FORMATTER); + this.to = to.format(STORAGE_FORMATTER); + } + + // Try to parse various date formats + private LocalDateTime parseDateTime(String dateTimeStr) { + try { + // Try to parse using standard formats like: + // yyyy-MM-dd HH:mm + return LocalDateTime.parse(dateTimeStr, STORAGE_FORMATTER); + } catch (Exception e1) { + try { + // Try to parse formats like d/M/yyyy HHmm (e.g., 2/12/2019 1800) + DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("d/M/yyyy HHmm"); + return LocalDateTime.parse(dateTimeStr, customFormatter); + } catch (Exception e2) { + throw new IllegalArgumentException("Could not parse date: " + dateTimeStr); + } + } + } + + public String getFrom() { + return from; + } + + public String getTo() { + return to; + } + + public LocalDateTime getFromDateTime() { + return fromDateTime; + } + + public LocalDateTime getToDateTime() { + return toDateTime; + } + + // For storage purposes, return the standardized date format + public String getFromForStorage() { + return fromDateTime != null ? fromDateTime.format(STORAGE_FORMATTER) : from; + } + + public String getToForStorage() { + return toDateTime != null ? toDateTime.format(STORAGE_FORMATTER) : to; + } + + @Override + public String toString() { + if (fromDateTime != null && toDateTime != null) { + // Display formatted dates if available + return "[E]" + super.toString() + " (from: " + fromDateTime.format(DISPLAY_FORMATTER) + + " to: " + toDateTime.format(DISPLAY_FORMATTER) + ")"; + } else { + // Fallback to original strings if date parsing failed + return "[E]" + super.toString() + " (from: " + from + " to: " + to + ")"; + } + } +} \ No newline at end of file diff --git a/src/main/java/oongaliegabangalieBot/task/Task.java b/src/main/java/oongaliegabangalieBot/task/Task.java new file mode 100644 index 000000000..f1f0a88de --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/task/Task.java @@ -0,0 +1,38 @@ +package oongaliegabangalieBot.task; + +public class Task { + protected String description; // description of task + protected boolean isDone; // whether task is completed + + // constructor of new task + public Task(String description) { + this.description = description; + this.isDone = false; + } + + // getter method for isDone + public boolean getIsDone() { + return isDone; + } + + public String getDescription() { return description; } + + // return string either X or space depending on task status + public String getStatusIcon() { + return (isDone ? "X" : " "); // mark done task with X + } + + public void markAsDone() { + this.isDone = true; + } + + public void markAsNotDone() { + this.isDone = false; + } + + // returns string representation of task (status + description) + // is called automatically when printing task + public String toString() { + return "[" + getStatusIcon() + "]" + " " + description; + } +} diff --git a/src/main/java/oongaliegabangalieBot/task/Todo.java b/src/main/java/oongaliegabangalieBot/task/Todo.java new file mode 100644 index 000000000..ac236b1c8 --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/task/Todo.java @@ -0,0 +1,14 @@ +package oongaliegabangalieBot.task; + +public class Todo extends Task { + + // constructs a new todo task with description + public Todo(String description) { + super(description); + } + + @Override + public String toString() { + return "[T]" + super.toString(); + } +} diff --git a/src/main/java/oongaliegabangalieBot/ui/Ui.java b/src/main/java/oongaliegabangalieBot/ui/Ui.java new file mode 100644 index 000000000..59da70efb --- /dev/null +++ b/src/main/java/oongaliegabangalieBot/ui/Ui.java @@ -0,0 +1,205 @@ +package oongaliegabangalieBot.ui; + +import java.util.ArrayList; +import java.util.Scanner; + +import oongaliegabangalieBot.exception.botException; +import oongaliegabangalieBot.task.Task; + +/** + * Handles all user interactions. + */ +public class Ui { + // Basic textual building blocks + private static final String DIVIDER = "____________________________________________________________"; + private static final String NEWLINE = System.lineSeparator(); + private static final String BOT_NAME = "Oongaliegabangalie"; + + private final Scanner scanner; + + /** + * Initializes the UI + */ + public Ui() { + scanner = new Scanner(System.in); + } + + /** + * Shows the welcome message + */ + public void showWelcome() { + String greeting = DIVIDER + NEWLINE + + "Hello! I'm " + BOT_NAME + NEWLINE + + "What can I do for you?" + NEWLINE + + DIVIDER; + System.out.println(greeting); + } + + /** + * Shows the goodbye message + */ + public void showGoodbye() { + String goodbye = DIVIDER + NEWLINE + + "Bye. Hope to see you again soon!" + NEWLINE + + "Oongaliegabangalie is always watching..." + NEWLINE + + DIVIDER; + + System.out.println(goodbye); + } + + /** + * Reads a command from the user + * @return the user's input + */ + public String readCommand() { + return scanner.nextLine(); + } + + /** + * Shows an error message + */ + public void showError(botException e) { + System.out.print(DIVIDER + NEWLINE); + System.out.print(e.getMessage()); + System.out.print(NEWLINE + DIVIDER + NEWLINE); + } + + /** + * Shows loading error message + */ + public void showLoadingError(String errorMessage) { + System.out.println(DIVIDER); + System.out.println("Warning: Error loading tasks: " + errorMessage); + System.out.println("Starting with an empty task list."); + System.out.println(DIVIDER); + } + + /** + * Shows message about loaded tasks + */ + public void showLoadedTasksMessage(int taskCount) { + System.out.println(DIVIDER); + System.out.println("I've loaded " + taskCount + " tasks from storage."); + System.out.println(DIVIDER); + } + + /** + * Shows a message after adding a task + */ + public void showAddedTask(Task task, int taskCount) { + System.out.println(DIVIDER); + System.out.println("Got it. I've added this task:"); + System.out.println(task); + System.out.println("Now you have " + taskCount + " tasks in the list."); + + // Short message based on task count + if (taskCount >= 20) { + System.out.println("you are so screwed..."); + } else if (taskCount >= 15) { + System.out.println("better knock a couple of these down before its too late!"); + } else if (taskCount >= 10) { + System.out.println("looks like your tasks are piling up..."); + } else if (taskCount >= 5) { + System.out.println("and so it begins... better not let it get out of hand"); + } + + System.out.println(DIVIDER); + } + + /** + * Shows a message after deleting a task + */ + public void showDeletedTask(Task task, int remainingTasks) { + System.out.println(DIVIDER); + System.out.println("Noted. I've removed this task:"); + System.out.println(task); + System.out.println("Now you have " + remainingTasks + " tasks in the list"); + System.out.println(DIVIDER); + } + + /** + * Shows all tasks in the list + */ + public void showTaskList(ArrayList tasks) { + System.out.println(DIVIDER); + System.out.println("Here are the tasks in your list:"); + + for (int i = 0; i < tasks.size(); i++) { + System.out.println((i + 1) + ". " + tasks.get(i)); + } + + System.out.println("Better get to it quick!"); + System.out.println(DIVIDER); + } + + /** + * Shows tasks on a specific date + */ + public void showTasksOnDate(ArrayList tasks, String dateStr) { + System.out.println(DIVIDER); + + if (tasks.isEmpty()) { + System.out.println("No tasks found on " + dateStr + "! you're free! (or I can't read your dates...)"); + } else { + System.out.println("Here are the tasks on " + dateStr + ":"); + + for (int i = 0; i < tasks.size(); i++) { + System.out.println((i + 1) + ". " + tasks.get(i)); + } + + if (tasks.size() > 2) { + System.out.println("Busy day ahead! better not procrastinate!"); + } else { + System.out.println("Not too busy, but you should still get on with it!"); + } + } + + System.out.println(DIVIDER); + } + + /** + * Shows a message after marking a task as done + */ + public void showMarkedDoneTask(Task task) { + System.out.println(DIVIDER); + System.out.println("Nice! I've marked this task as done:"); + System.out.println(" " + task); + System.out.println("Now go do something else and stop bothering me!"); + System.out.println(DIVIDER); + } + + /** + * Shows a message after marking a task as not done + */ + public void showMarkedNotDoneTask(Task task) { + System.out.println(DIVIDER); + System.out.println("OK, I've marked this task as not done yet:"); + System.out.println(" " + task); + System.out.println("You better get to it..."); + System.out.println(DIVIDER); + } + + /** + * Shows tasks matching a keyword + */ + public void showMatchingTasks(ArrayList tasks, String keyword) { + System.out.println(DIVIDER); + + if (tasks.isEmpty()) { + System.out.println("No tasks matched the keyword '" + keyword + "'!"); + System.out.println("Maybe try another search term? Or are you sure you spelled it right?"); + } else { + System.out.println("Here are the matching tasks in your list:"); + + for (int i = 0; i < tasks.size(); i++) { + System.out.println((i + 1) + "." + tasks.get(i)); + } + + if (tasks.size() > 3) { + System.out.println("Wow that's a lot of matches! You sure use '" + keyword + "' a lot!"); + } + } + + System.out.println(DIVIDER); + } +} \ No newline at end of file