diff --git a/.gitignore b/.gitignore index 2873e189e..4e85e30b0 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT + +/data/ + diff --git a/docs/README.md b/docs/README.md index 8077118eb..b543d988c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,143 @@ -# User Guide +# Floda User Guide -## Features +Floda is a command-line to-do list application designed to help you manage your tasks efficiently. It allows you to add, delete, mark as done, and list tasks with ease. +## Quick Start -### Feature-ABC +Prerequisites: JDK 11, Intellij. -Description of the feature. +1. Ensure you have Java installed on your computer. +2. Download the latest Floda.jar file from [here](https://github.com/shawnpong/ip/releases). +3. Place the downloaded file in your desired directory. +4. Open up a command prompt and navigate to the directory Floda.jar is in. + - You can do so on windows by typing 'cmd' in the file explorer address bar. +5. Run the application using the command 'java -jar Floda.jar'. +6. A CLI similar to the below should appear. +![img.png](img.png) -### Feature-XYZ +## Features +### Adding a Todo: `todo` -Description of the feature. +Add a task with description to your to-do list -## Usage +Format: `todo ` -### `Keyword` - Describe action +Replace `` with a brief description of your task. -Describe the action and its outcome. +Example Input: `todo Buy Groceries` -Example of usage: +Example Output: `Added: [T] [ ] Buy groceries` -`keyword (optional arguments)` +### Adding a Deadline: `deadline` -Expected outcome: +Add a task with description and a deadline to your to-do list + +Format: `deadline /by ` + +Replace `` with a brief description of your task and `` with the due date/time. + +Example Input: `deadline Submit report /by 2024-03-10` + +Example Output: `Added: [D] [ ] Submit report (by: 2024-03-10)` + +### Adding a Event: `event` + +Add a task with description and a start and end time to your to-do list + +Format: `event /from /to ` + +Replace `` with a brief description of your task, `` with the event start time, and `` with the event end time. + +Example Input: `event Team meeting /from 2024-03-08 14:00 /to 2024-03-08 16:00` + +Example Output: `Added: [E] [ ] Team meeting (from: 2024-03-08 14:00 to: 2024-03-08 16:00)` + +### Listing Tasks: `list` + +To view all tasks in your to-do list so far + +Format: `list` + +Example Output: ` [] [] (additional details)` +1. [T] [ ] Buy groceries +2. [D] [ ] Submit report (by: 2024-03-10) +3. [E] [ ] Team meeting (from: 2024-03-08 14:00 to: 2024-03-08 16:00) + +### Deleting a Task: `delete` + +To delete a specific task in your todo list + +Format: `delete ` + +Replace `` with the number corresponding to the task you want to delete. +> Tip: Use the command `list` to find the `` of the task you wish you delete + +### Marking a Task as Done: `mark` + +Mark a specific task as done in your to-do list + +Format: `mark ` + +Replace `` with the number corresponding to the task you want to mark as done. +> Tip: Use the command `list` to find the `` of the task you wish to mark as done. + +Example Input: `mark 1` + +Example Output: `I have marked this task as done: [T] [X] Buy Groceries` + +### Unmarking a Task: `unmark` + +Remove the completion status of a specific task in your to-do list + +Format: `unmark ` + +Replace `` with the number corresponding to the task you want to unmark. +> Tip: Use the command `list` to find the `` of the task you wish to unmark. + +Example Input: `unmark 1` + +Example Output: `I have marked this task as not done: [T] [ ] Buy Groceries` + + +### Finding Tasks: `find` + +Search for tasks containing a specific keyword in your to-do list + +Format: `find ` + +Replace `` with the keyword you want to search for in your tasks. + +Example Input: `find meeting` + +Example Output: `Here are the matching tasks in your list: +1.[E] [ ] Team meeting (from: 2024-03-08 14:00 to: 2024-03-08 16:00)` + +### Exiting the Application: `bye` + +Exits the Floda application + +Format: `bye` + +### Saving tasks + +Tasks are automatically saved to the tasks.txt file in the application directory after any command that modifies the task list. + +### Editing the Data File + +You can directly edit the tasks.txt file to update tasks. Ensure the file format remains valid to avoid data loss. +>Caution: If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it. + +## Command Summary + +| Command | Description | Example Usage | +|---------------------------------------------------------|------------------------------------------------------------|--------------------------------| +| `todo ` | Add a Todo task to the list with a description. | `todo Buy Groceries` | +| `deadline /by ` | Add a Deadline task with a description and deadline. | `deadline Submit report /by 2024-03-10` | +| `event /from /to ` | Add an Event task with a description and time frame. | `event Team meeting /from 2024-03-08 14:00 /to 2024-03-08 16:00` | +| `list` | View all tasks in the to-do list. | `list` | +| `delete ` | Delete a specific task from the list. | `delete 1` | +| `mark ` | Mark a task as done. | `mark 1` | +| `unmark ` | Remove the completion status of a task. | `unmark 1` | +| `find ` | Search for tasks containing a specific keyword. | `find meeting` | +| `bye` | Exit the application. | `bye` | -Description of the outcome. -``` -expected output -``` diff --git a/docs/img.png b/docs/img.png new file mode 100644 index 000000000..e48f8cccb Binary files /dev/null and b/docs/img.png differ diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java new file mode 100644 index 000000000..46534e3c2 --- /dev/null +++ b/src/main/java/Deadline.java @@ -0,0 +1,39 @@ +/** + * The Deadline class represents a task with a deadline. + */ +public class Deadline extends Task { + /** The deadline of the task. */ + protected String by; + + /** + * Constructs a Deadline object with the specified description, deadline, and completion status. + * + * @param description The description of the deadline task. + * @param by The deadline of the task. + * @param isDone The completion status of the task. + */ + public Deadline(String description, String by, Boolean isDone) { + super(description); + this.by = by; + this.isDone = isDone; + } + + /** + * Retrieves the deadline of the task. + * + * @return The deadline of the task. + */ + public String getBy() { + return by; + } + + /** + * Returns a string representation of the deadline task. + * + * @return A string representation of the deadline task. + */ + @Override + public String toString() { + return "[D] " + super.getStatusIcon() + " " + super.toString() + " (by: " + by + ")"; + } +} 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/Events.java b/src/main/java/Events.java new file mode 100644 index 000000000..8fcaacd99 --- /dev/null +++ b/src/main/java/Events.java @@ -0,0 +1,52 @@ +/** + * The Events class represents a task that spans over a period of time. + */ +public class Events extends Task { + /** The start time of the event. */ + protected String from; + /** The end time of the event. */ + protected String to; + + /** + * Constructs an Events object with the specified description, start time, end time, and completion status. + * + * @param description The description of the event task. + * @param from The start time of the event. + * @param to The end time of the event. + * @param isDone The completion status of the event task. + */ + public Events(String description, String from, String to, Boolean isDone) { + super(description); + this.from = from; + this.to = to; + this.isDone = isDone; + } + + /** + * Retrieves the start time of the event. + * + * @return The start time of the event. + */ + public String getFrom() { + return from; + } + + /** + * Retrieves the end time of the event. + * + * @return The end time of the event. + */ + public String getTo() { + return to; + } + + /** + * Returns a string representation of the event task. + * + * @return A string representation of the event task. + */ + @Override + public String toString() { + return "[E] " + super.getStatusIcon() + " " + super.toString() + " (from: " + from + " to: " + to + ")"; + } +} diff --git a/src/main/java/Floda.java b/src/main/java/Floda.java new file mode 100644 index 000000000..25a89d253 --- /dev/null +++ b/src/main/java/Floda.java @@ -0,0 +1,40 @@ +import java.util.Scanner; +import java.io.FileNotFoundException; + + +/** + * Main class for the Floda application. + */ +public class Floda { + private static final String NAME = "Floda"; + public static final String FILE_PATH = "./data/tasks.txt"; + private static final Ui ui = new Ui(); + private static final Storage storage = new Storage(FILE_PATH); + private static final TaskList tasks = new TaskList(storage); + + /** + * Main method to run the Floda application. + * + * @param args The command-line arguments. + */ + public static void main(String[] args) { + ui.showWelcomeMessage(NAME); + try { + tasks.loadTasks(); + } catch (FileNotFoundException | InvalidInputException e) { + ui.showErrorMessage(e.getMessage()); + } + + Scanner scanner = new Scanner(System.in); + ui.showInstructions(); + while (TaskList.isActive) { + try { + String line = scanner.nextLine().trim(); + Parser.parseCommand(line); + } catch (InvalidInputException e) { + ui.showErrorMessage(e.getMessage()); + } + } + scanner.close(); + } +} diff --git a/src/main/java/InvalidInputException.java b/src/main/java/InvalidInputException.java new file mode 100644 index 000000000..016fcc7ff --- /dev/null +++ b/src/main/java/InvalidInputException.java @@ -0,0 +1,13 @@ +/** + * The InvalidInputException class represents an exception that is thrown when the input provided is invalid. + */ +public class InvalidInputException extends Exception { + /** + * Constructs an InvalidInputException with the specified error message. + * + * @param errorMessage The error message describing the nature of the invalid input. + */ + public InvalidInputException(String errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..80c604436 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: Floda + diff --git a/src/main/java/Parser.java b/src/main/java/Parser.java new file mode 100644 index 000000000..febb87098 --- /dev/null +++ b/src/main/java/Parser.java @@ -0,0 +1,106 @@ +/** + * The Parser class is responsible for parsing commands and file lines. + */ +public class Parser { + + /** + * Parses the command provided in the input line. + * + * @param line The input line containing the command. + * @throws InvalidInputException If the input command is invalid. + */ + public static void parseCommand(String line) throws InvalidInputException { + String[] parts = line.split(" ", 2); + String command = parts[0].trim(); + + final String COMMAND_BYE = "bye"; + final String COMMAND_LIST = "list"; + final String COMMAND_MARK = "mark"; + final String COMMAND_UNMARK = "unmark"; + final String COMMAND_DEADLINE = "deadline"; + final String COMMAND_TODO = "todo"; + final String COMMAND_EVENT = "event"; + final String COMMAND_DELETE = "delete"; + final String COMMAND_FIND = "find"; + + switch (command) { + case COMMAND_BYE: + TaskList.handleByeTask(); + TaskList.setActive(false); + break; + case COMMAND_LIST: + TaskList.handleListTask(); + break; + case COMMAND_MARK: + TaskList.handleMarkTask(line); + Storage.saveToFile(); + break; + case COMMAND_UNMARK: + TaskList.handleUnmarkTask(line); + Storage.saveToFile(); + break; + case COMMAND_DEADLINE: + TaskList.handleDeadlineTask(line); + Storage.saveToFile(); + break; + case COMMAND_TODO: + TaskList.handleTodoTask(line); + Storage.saveToFile(); + break; + case COMMAND_EVENT: + TaskList.handleEventTask(line); + Storage.saveToFile(); + break; + case COMMAND_DELETE: + TaskList.handleDeleteTask(line); + Storage.saveToFile(); + break; + case COMMAND_FIND: + TaskList.handleFindTask(line); + break; + default: + throw new InvalidInputException("Invalid command: " + command); + } + } + + /** + * Parses a line from the file and adds the corresponding task to the task list. + * + * @param line The line to be parsed from the file. + * @throws InvalidInputException If the input format in the file is invalid. + */ + public static void parseFileLine(String line) throws InvalidInputException { + String[] parts = line.split("\\|"); + + if (parts.length < 3) { + throw new InvalidInputException("Invalid input format in file"); + } + + String type = parts[0].trim(); + boolean isDone = parts[1].trim().equals("1"); + String description = parts[2].trim(); + + switch (type) { + case "T": + TaskList.list.add(new ToDo(description, isDone)); + break; + case "D": + if (parts.length < 4) { + throw new InvalidInputException("Invalid input format for deadline in file"); + } + String by = parts[3].trim(); + TaskList.list.add(new Deadline(description, by, isDone)); + break; + case "E": + if (parts.length < 5) { + throw new InvalidInputException("Invalid input format for event in file"); + } + String from = parts[3].trim(); + String to = parts[4].trim(); + TaskList.list.add(new Events(description, from, to, isDone)); + break; + default: + throw new InvalidInputException("Unknown task type in file"); + } + } +} diff --git a/src/main/java/Storage.java b/src/main/java/Storage.java new file mode 100644 index 000000000..898c17fd0 --- /dev/null +++ b/src/main/java/Storage.java @@ -0,0 +1,101 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Scanner; + +/** + * The Storage class handles file operations for saving and loading tasks. + */ +public class Storage { + private static String FILE_PATH; + + /** + * Constructs a Storage object with the specified file path. + * + * @param filePath The path to the file where tasks will be stored. + */ + public Storage(String filePath) { + FILE_PATH = filePath; + } + + /** + * Saves tasks to the file. + */ + public static void saveToFile() { + try { + FileWriter fw = new FileWriter(FILE_PATH); + for (Task task : TaskList.getList()) { + fw.write(taskToLine(task) + "\n"); + } + fw.close(); + System.out.println("Saved to file"); + } catch (IOException e) { + System.out.println("Error saving tasks to file: " + e.getMessage()); + } + } + + /** + * Loads tasks from the file. + * + * @throws FileNotFoundException If the tasks file is not found. + * @throws InvalidInputException If there is an invalid input in the file. + */ + public void loadTasks() throws FileNotFoundException { + File file = new File(FILE_PATH); + if (!file.exists()) { + createFileAndFolder(); + return; + } + try { + Scanner scanner = new Scanner(file); + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + Parser.parseFileLine(line); + } + scanner.close(); + } catch (InvalidInputException e) { + // If invalid input is encountered, delete the file and create a new one + file.delete(); + createFileAndFolder(); + System.out.println("Invalid input format in file. Deleting file and creating a new one."); + } + } + + /** + * Creates the file and its parent folder if they don't exist. + */ + private void createFileAndFolder() { + try { + File file = new File(FILE_PATH); + File parentDir = file.getParentFile(); + if (!parentDir.exists()) { + parentDir.mkdirs(); + } + file.createNewFile(); + System.out.println("Folder and file created at: " + FILE_PATH); + } catch (IOException e) { + System.out.println("Error creating folder and file: " + e.getMessage()); + } + } + + /** + * Converts a task object to a string representation for writing to the file. + * + * @param task The task object to convert. + * @return A string representing the task for writing to the file. + */ + private static String taskToLine(Task task) { + if (task instanceof ToDo) { + ToDo todo = (ToDo) task; + return "T | " + (todo.isDone() ? "1" : "0") + " | " + todo.getDescription(); + } else if (task instanceof Deadline) { + Deadline deadline = (Deadline) task; + return "D | " + (deadline.isDone() ? "1" : "0") + " | " + deadline.getDescription() + " | " + deadline.getBy(); + } else if (task instanceof Events) { + Events event = (Events) task; + return "E | " + (event.isDone() ? "1" : "0") + " | " + event.getDescription() + " | " + event.getFrom() + " | " + event.getTo(); + } + return ""; + } +} diff --git a/src/main/java/Task.java b/src/main/java/Task.java new file mode 100644 index 000000000..c7b86517d --- /dev/null +++ b/src/main/java/Task.java @@ -0,0 +1,74 @@ +/** + * The Task class represents a task with a description and completion status. + */ +public class Task { + /** The description of the task. */ + protected String description; + /** The completion status of the task. */ + protected boolean isDone; + + /** + * Constructs a Task object with the specified description. + * + * @param description The description of the task. + */ + public Task(String description) { + this.description = description; + this.isDone = false; + } + + /** + * Retrieves the description of the task. + * + * @return The description of the task. + */ + public String getDescription() { + return description; + } + + /** + * Sets the description of the task. + * + * @param description The description to set. + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Checks if the task is done. + * + * @return True if the task is done, otherwise false. + */ + public boolean isDone() { + return isDone; + } + + /** + * Sets the completion status of the task. + * + * @param done The completion status to set. + */ + public void setDone(boolean done) { + isDone = done; + } + + /** + * Retrieves the status icon of the task. + * + * @return The status icon of the task. + */ + public String getStatusIcon() { + return (isDone ? "[X]" : "[ ]"); + } + + /** + * Returns a string representation of the task. + * + * @return A string representation of the task. + */ + @Override + public String toString() { + return description; + } +} diff --git a/src/main/java/TaskList.java b/src/main/java/TaskList.java new file mode 100644 index 000000000..7db11464c --- /dev/null +++ b/src/main/java/TaskList.java @@ -0,0 +1,247 @@ +import java.util.Scanner; +import java.util.ArrayList; +import java.io.FileNotFoundException; + +/** + * The TaskList class manages the list of tasks and provides methods to handle various tasks. + */ +public class TaskList { + /** The list of tasks. */ + public static ArrayList list = new ArrayList<>(); + /** The storage object to interact with file storage. */ + private final Storage storage; + /** Indicates whether the task list is active. */ + static Boolean isActive = true; + + /** + * Constructs a TaskList object with the specified storage. + * + * @param storage The storage object to interact with file storage. + */ + public TaskList(Storage storage) { + this.storage = storage; + } + + /** + * Sets the activity status of the task list. + * + * @param value The value to set for the activity status. + */ + public static void setActive(Boolean value) { + isActive = value; + } + + /** + * Loads tasks from storage. + * + * @throws FileNotFoundException If the tasks file is not found. + * @throws InvalidInputException If the input format in the file is invalid. + */ + public void loadTasks() throws FileNotFoundException, InvalidInputException { + storage.loadTasks(); + } + + /** + * Retrieves the list of tasks. + * + * @return The list of tasks. + */ + public static ArrayList getList() { + return list; + } + + /** + * Handles the "bye" command. + */ + static void handleByeTask() { + System.out.println("Bye. Hope to see you again soon!"); + } + + /** + * Handles the "list" command. + */ + static void handleListTask() { + if (list.isEmpty()) { + System.out.println("Your to-do list is empty."); + } else { + System.out.println("List so far: "); + for (int i = 0; i < list.size(); i++) { + System.out.println((i + 1) + "." + list.get(i)); + } + } + } + + /** + * Handles the "delete" command. + * + * @param line The command line. + * @throws InvalidInputException If the task number is invalid. + */ + static void handleDeleteTask(String line) throws InvalidInputException { + Scanner taskScanner = new Scanner(line); + taskScanner.next(); + if (taskScanner.hasNextInt()) { + int taskNumber = taskScanner.nextInt() - 1; + if (isValidTaskNumber(taskNumber)) { + System.out.println("Deleting task: " + list.get(taskNumber)); + list.remove(taskNumber); + System.out.println("Task deleted successfully!"); + } else { + throw new InvalidInputException("Invalid task number! Please check with 'list'."); + } + } + } + + /** + * Handles the "mark" command. + * + * @param line The command line. + * @throws InvalidInputException If the task number is invalid or the input is incorrect. + */ + static void handleMarkTask(String line) throws InvalidInputException { + Scanner taskScanner = new Scanner(line); + taskScanner.next(); + if (taskScanner.hasNextInt()) { + int taskNumber = taskScanner.nextInt() - 1; + if (isValidTaskNumber(taskNumber)) { + list.get(taskNumber).setDone(true); + System.out.println("I have marked this task as done: " + list.get(taskNumber)); + } else { + throw new InvalidInputException("Invalid task number! Please check with 'list'."); + } + } else { + throw new InvalidInputException("Invalid input! Please check with 'list'."); + } + taskScanner.close(); + } + + /** + * Handles the "unmark" command. + * + * @param line The command line. + * @throws InvalidInputException If the task number is invalid or the input is incorrect. + */ + static void handleUnmarkTask(String line) throws InvalidInputException { + Scanner taskScanner = new Scanner(line); + taskScanner.next(); + if (taskScanner.hasNextInt()) { + int taskNumber = taskScanner.nextInt() - 1; + if (isValidTaskNumber(taskNumber)) { + list.get(taskNumber).setDone(false); + System.out.println("I have marked this task as not done: " + list.get(taskNumber)); + } else { + throw new InvalidInputException("Invalid task number! Please check with 'list'."); + } + } else { + throw new InvalidInputException("Invalid input! Please check with 'list'."); + } + taskScanner.close(); + } + + /** + * Handles the "deadline" command. + * + * @param line The command line. + * @throws InvalidInputException If the input format is incorrect. + */ + static void handleDeadlineTask(String line) throws InvalidInputException { + Scanner taskScanner = new Scanner(line); + taskScanner.next(); + String remaining = taskScanner.nextLine().trim(); + int byIndex = remaining.indexOf("/by"); + if (byIndex == -1 || byIndex == 0) { + throw new InvalidInputException("Invalid input format! Use: 'deadline /by