diff --git a/docs/README.md b/docs/README.md index 8077118eb..28a158314 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,104 @@ # User Guide -## Features +## Features -### Feature-ABC +Users can add, mark, unmark, list tasks as needed. -Description of the feature. +### Add task -### Feature-XYZ +Users can add new tasks to the list. Tasks have 3 available types, Event, Todo and Deadline -Description of the feature. +Format: `type of task` `name of task` date-attribute of task +Example: deadline do tutorial /by Sunday -## Usage +Expected outcome: +New tasks of correct type and description is added to task list + +### Add Todo (task subclass) + +Users can add todo tasks to the list. + +Format: todo `name of task` +Example: todo eat + +Expected: task of type T is added to task list + +### Add Event (task subclass) + +Users can add Events to the list. + +Format: event `name of event` /from `start time` /to `end time` +Example: event eat /from morning /to night + +Expected: task of type E is added to task list + +### Add Deadline (task subclass) + +Users can add Deadline to the list. + +Format: deadline `name of deadline` /by `due date` +Example (correct LocalDate format): deadline return books /by 2022-03-05 +Example (incorrect LocalDate format): deadline return books /by next week + +Expected: task of type D is added to task list, user is alerted if date was not stored as LocalDate format + +### Mark task + +Users can mark tasks as done. + +Format: mark `task index` +Example: mark 3 + +Expected outcome: +[ ] state of task(s) changes to [x] when marked as done. + +### Unmark task + +Users can mark tasks as undone. -### `Keyword` - Describe action +Format: unmark `task index` +Example: mark 3 + +Expected outcome: +[x] state of task(s) changes to [ ] when marked as done. -Describe the action and its outcome. +### List task(s) -Example of usage: +Users can list and view all tasks currently in the task list -`keyword (optional arguments)` +Format: list Expected outcome: +If task list is empty, "Tasklist is empty." +Indexed list of `index`, `done state`, `description` -Description of the outcome. +### List overdue task(s) + +Users can list and view all tasks that are currently overdue in the task list + +Format: list overdue + +Expected outcome: +If task list is empty, "Tasklist is empty." +Indexed list of overdue tasks `index`, `done state`, `description` -``` -expected output -``` +### Find + +Users can find and list all tasks by inputting keyword to be searched + +Format: find `string to be searched` +Example: find food + +Expected outcome: +If task lsit is empty, "Tasklist is empty." +Indexed list of tasks with description containing searched string + +### Delete + +Users can remove tasks from task list +Format: delete `index` +Example: delete 3 + +Expected outcome: +If index out of bounds, "Please input valid task number!" +"Noted. I've removed this task:" `index` diff --git a/duke.txt b/duke.txt new file mode 100644 index 000000000..e2aae9be5 --- /dev/null +++ b/duke.txt @@ -0,0 +1 @@ +D|false|eat /by tmr diff --git a/ip.jar b/ip.jar new file mode 100644 index 000000000..0f2f09b55 Binary files /dev/null and b/ip.jar differ diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java new file mode 100644 index 000000000..6efbb53fd --- /dev/null +++ b/src/main/java/Deadline.java @@ -0,0 +1,61 @@ +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +//import java.time.temporal.ChronoUnit; + +/* + * Sub-class of super-class Task, represents a task that has a deadline, + * Contains attributes due, info, deadline name and evaluates attribute whether Deadline task is overdue + */ +public class Deadline extends Task { + public LocalDate due; + public String dueText; + public String[] info; + public String deadlineName; + public boolean isOverdue; + + public Deadline(String description) { + super(description); + this.info = this.description.split("/by", 2); + this.deadlineName = info[0]; + this.due = isValid((info[1]).trim()); + if (this.due == null) { + this.dueText = (info[1]).trim(); + System.out.println("Due date is not in yyyy-MM-dd format, will be saved as text"); + } else if (this.due.isBefore(LocalDate.now())) { + this.isOverdue = true; + } + + } + + private LocalDate isValid(String date) { + try { + return LocalDate.parse(date); + } catch (Exception e) { + return null; + } + } + + @Override + public String fileFormat() { + DateTimeFormatter yyyyMMdd = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + if (this.due != null) { + return (String.format("D|%b|%s /by %s\n", super.isDone, this.deadlineName, + due.format(yyyyMMdd))); + } else { + return (String.format("D|%b|%s /by %s\n", super.isDone, this.deadlineName, this.dueText)); + } + } + + @Override + public String toString() { + DateTimeFormatter yyyyMMdd = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + if (this.due != null) { + return ("[D][" + super.getStatusIcon() + "] " + this.deadlineName) + + " (by: " + this.due.format(yyyyMMdd) + ")"; + } else { + return ("[D][" + super.getStatusIcon() + "] " + this.deadlineName) + + " (by: " + this.dueText + ")"; + } + } +} diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 5d313334c..78852b3d1 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,10 +1,30 @@ +/* + * Duke is an instance of the bot which has several subclasses, + * storage for file management, ui for communicating with user for input/output, tasklist for managing tasks and task-related commands + */ public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); + + private String fileName = "duke.txt"; + public Storage storage; + private UI ui; + public TaskList tasks; + + public Duke() { + ui = new UI(); + storage = new Storage(fileName); + this.tasks = new TaskList(); + storage.load(tasks); + ui.greet(tasks); } + + // public void addTask(String taskName){ + // Task t = new Task(taskName); + // taskList.add(t); + // System.out.printf( + // "Got it. I've added this task:\n" + + // String.format("added: %s\n", taskName) + + // ); + // } + } diff --git a/src/main/java/Event.java b/src/main/java/Event.java new file mode 100644 index 000000000..8da2927df --- /dev/null +++ b/src/main/java/Event.java @@ -0,0 +1,27 @@ +/* + * Sub-class of super-class Task, represents a task that is an event, + * Contains attributes event name, start and end time + */ +public class Event extends Task { + public String startTime, endTime, eventName; + public String[] info; + + public Event(String description) { + super(description); + this.info = this.description.split("/from", 2); + this.eventName = info[0]; + this.startTime = info[1].split("/to", 2)[0]; + this.endTime = info[1].split("/to", 2)[1]; + } + + @Override + public String fileFormat() { + return (String.format("E|%b|%s /from %s /to %s\n", super.isDone, this.eventName, this.startTime, this.endTime)); + } + + @Override + public String toString() { + return ("[E][" + super.getStatusIcon() + "] " + this.eventName) + + " (from: " + this.startTime + " to: " + this.endTime + ")"; + } +} diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 000000000..fe0329cbb --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,58 @@ +import java.util.Scanner; + +/* + * Initialises Duke class + * If earlier written file exists, displays files + * Continuously promtps user for input until specified terminating input is received + * List of inputs include bye, list, mark, unmark, event, todo, deadline, delete, find + */ +public class Main { + public static void main(String[] args) { + Duke Duke = new Duke(); + Scanner userInput = new Scanner(System.in); // create Scanner object + String inputCommand; // read user input + String[] commandPhrase; + String command, phrase; + while (true) { + inputCommand = userInput.nextLine(); + commandPhrase = inputCommand.split(" ", 2); + command = commandPhrase[0]; + if (commandPhrase.length < 2) { + if (command.equals("bye")) { + break; + } else if (command.equals("clear")) { + Duke.tasks.clearTaskList(); + } else if (command.equals("list")) { + Duke.tasks.list(); + } else if (command.equals("overdue")) { + Duke.tasks.listOverdue(); + } else { + System.out.println("Invalid command"); + } + } else { + phrase = commandPhrase[1]; + if (command.equals("mark")) { + Duke.tasks.changeTaskState(true, Integer.parseInt(phrase)); + } else if (command.equals("unmark")) { + Duke.tasks.changeTaskState(false, Integer.parseInt(phrase)); + } else if (command.equals("event")) { + Duke.tasks.addEvent(phrase); + } else if (command.equals("todo")) { + Duke.tasks.addTodo(phrase); + } else if (command.equals("deadline")) { + Duke.tasks.addDeadline(phrase); + } else if (command.equals("delete")) { + Duke.tasks.delete(Integer.parseInt(phrase)); + } else if (command.equals("find")) { + Duke.tasks.find(phrase); + } else { + System.out.println("Invalid command"); + } + } + Duke.storage.saveToFile(Duke.tasks); + UI.horizontalLine(); + } + userInput.close(); + System.out.println("Bye. Hope to see you again soon!"); + } +} diff --git a/src/main/java/Storage.java b/src/main/java/Storage.java new file mode 100644 index 000000000..bd59f55e4 --- /dev/null +++ b/src/main/java/Storage.java @@ -0,0 +1,82 @@ +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Scanner; +import java.util.ArrayList; + +/* + * Storage class handles all file methods of Duke, it only has the attribute fileName + */ +public class Storage { + private String fileName; + + public Storage(String inputFileName) { + this.fileName = inputFileName; + } + + /* + * Loads file from location specified by fileName + * + * @param none + * + * @return File object file from the location + */ + public void load(TaskList taskList) { + File file = new File(fileName); + String data; + try { + if (!file.createNewFile()) { + Scanner fileData = new Scanner(file); + while (fileData.hasNext()) { + data = fileData.nextLine(); + String[] inputArgs = data.split("\\|"); + addFileData(inputArgs, taskList); + for (String arg : inputArgs) { + System.out.print(arg + " "); + } + } + } + } catch (IOException e) { + System.out.print("\nError getting file data"); + } + } + + public void addFileData(String[] inputArgs, TaskList taskList) { + Task newTask; + String command = inputArgs[0]; + boolean taskStatus = Boolean.parseBoolean(inputArgs[1]); + switch (command) { + case "T": + newTask = new Todo(inputArgs[2]); + break; + case "D": + newTask = new Deadline(inputArgs[2]); + break; + case "E": + newTask = new Event(inputArgs[2]); + break; + default: + throw new IllegalStateException("File contents are invalid"); + } + if (taskStatus) { + newTask.markAsDone(); + } + taskList.returnTaskList().add(newTask); + } + + /* + * Saves file to destination specified by fileName, the same + * location where file is retrieved from + */ + public void saveToFile(TaskList taskList) { + try { + FileWriter fWriter = new FileWriter(fileName); + for (Task task : taskList.returnTaskList()) { + fWriter.write(task.fileFormat()); + } + fWriter.close(); + } catch (IOException e) { + System.out.print("IOException Error: data not saved to file\n"); + } + } +} diff --git a/src/main/java/Task.java b/src/main/java/Task.java new file mode 100644 index 000000000..d51c2b50e --- /dev/null +++ b/src/main/java/Task.java @@ -0,0 +1,40 @@ +/* + * Superclass Task extends to subtasks Deadline, Event and Todo + * Task has 2 attributes, description and isDone, which toggles whether the task is done or not done + */ +public class Task { + protected String description; + protected boolean isDone; + + public Task(String description) { + this.description = description; + this.isDone = false; + } + + public String getStatusIcon() { + return (isDone ? "X" : " "); // mark done task with X + } + + public void markAsDone() { + this.isDone = true; + System.out.println("Nice! I've marked this task as done:"); + System.out.println(this.toString()); + } + + public void markAsUndone() { + this.isDone = false; + System.out.println("OK, I've marked this task as not done yet:"); + System.out.println(this.toString()); + } + + public String fileFormat() { + return (String.format(" |%b|%s", this.isDone, this.description)); + } + + @Override + public String toString() { + return String.format("[%s] %s", this.getStatusIcon(), this.description); + } + + // ... +} \ No newline at end of file diff --git a/src/main/java/TaskList.java b/src/main/java/TaskList.java new file mode 100644 index 000000000..6f1a59492 --- /dev/null +++ b/src/main/java/TaskList.java @@ -0,0 +1,146 @@ +import java.util.ArrayList; +import java.util.List; +import java.io.File; +import java.util.Scanner; +import java.io.IOException; + +/* + * TaskList is the class of Duke which contains the list of tasks and methods to makes changes to the list + * It contains only the ArrayList tasks + */ +public class TaskList { + private List tasks = new ArrayList(); + + /* + * Writes file data from file scanned from file to tasks object + * Uses Scanner to read contents from file, splits content into + * String[] + * + * @param File file with contents of previously created tasklist + * + * @throws IOException if file data is not formatted as specified + */ + public void Tasklist() { + this.tasks = new ArrayList(); + } + + /* + * Formats file data retrieved by Tasklist, parses String[] and adds them to + * tasks as Todo, Event or Deadline subclasses + */ + + public List returnTaskList() { + return tasks; + } + + public void clearTaskList() { + tasks.clear(); + System.out.println("Task list cleared!"); + } + + public void addEvent(String taskName) { + Event t = new Event(taskName); + tasks.add(t); + System.out.printf( + "Got it. I've added this task:\n" + + t.toString() + + String.format("\nNow you have %d tasks in the list.\n", tasks.size())); + } + + public void addTodo(String taskName) { + Todo t = new Todo(taskName); + tasks.add(t); + System.out.printf( + "Got it. I've added this task:\n" + + t.toString() + + String.format("\nNow you have %d tasks in the list.\n", tasks.size())); + } + + public void addDeadline(String taskName) { + Deadline t = new Deadline(taskName); + tasks.add(t); + System.out.printf( + "Got it. I've added this task:\n" + + t.toString() + + String.format("\nNow you have %d tasks in the list.\n", tasks.size())); + } + + public void changeTaskState(boolean doneState, Integer index) { + if (index > tasks.size() || index < 1) { + System.out.println("Please input valid task number!"); + } else { + index--; + if (doneState) { + tasks.get(index).markAsDone(); + } else { + tasks.get(index).markAsUndone(); + } + } + } + + public void delete(int index) { + if (index > tasks.size() || index < 1) { + System.out.println("Please input valid task number!"); + } else { + index--; + System.out.printf("Noted. I've removed this task:" + + tasks.get(index).toString()); + tasks.remove(index); + System.out.println(String.format("\nNow you have %d tasks in the list.\n", tasks.size())); + } + } + + /* + * Main list method, lists all contents of tasklist + */ + public void list() { + if (tasks.size() == 0) { + System.out.println("Task list is empty."); + } else { + System.out.println("Here are the tasks in your list:"); + Integer i = 0; + for (Task task : tasks) { + System.out.printf(String.format("%d.%s\n", i + 1, task.toString())); + i++; + } + } + } + + /* + * Lists overdue deadline subclass objects in tasklist + */ + public void listOverdue() { + if (tasks.size() == 0) { + System.out.println("Task list is empty."); + } else { + System.out.println("Here are the overdue deadlines in your list:"); + Integer i = 0; + for (Task task : tasks) { + if (task instanceof Deadline && ((Deadline) task).isOverdue) { + System.out.printf(String.format("%d.%s\n", i + 1, task.toString())); + i++; + } + } + } + } + + /* + * lists all tasks in task list containing keyword + * + * @param keyword to be searched for + */ + public void find(String keyword) { + if (tasks.size() == 0) { + System.out.println("Task list is empty."); + } else { + System.out.println("Here are the matching tasks in your list:"); + Integer i = 0; + for (Task task : tasks) { + if (task.description.contains(keyword)) { + System.out.printf(String.format("%d.%s\n", i + 1, task.toString())); + i++; + } + } + } + } +} diff --git a/src/main/java/Todo.java b/src/main/java/Todo.java new file mode 100644 index 000000000..c236f72a3 --- /dev/null +++ b/src/main/java/Todo.java @@ -0,0 +1,19 @@ +/* + * Sub-class of super-class Task, represents a todo task, + * only inherits task description attribute from parent Task class + */ +public class Todo extends Task { + public Todo(String description) { + super(description); + } + + @Override + public String toString() { + return ("[T][" + super.getStatusIcon() + "] " + super.description); + } + + @Override + public String fileFormat() { + return (String.format("T|%b|%s\n", super.isDone, this.description)); + } +} diff --git a/src/main/java/UI.java b/src/main/java/UI.java new file mode 100644 index 000000000..17134312f --- /dev/null +++ b/src/main/java/UI.java @@ -0,0 +1,23 @@ +/* + * UI class contains methods that interact with user through text + */ +public class UI { + /* + * Prints greeting statement and logo to terminal + */ + public void greet(TaskList taskList) { + String logo = " ____ _ \n" + + "| _ \\ _ _| | _____ \n" + + "| | | | | | | |/ / _ \\\n" + + "| |_| | |_| | < __/\n" + + "|____/ \\__,_|_|\\_\\___|\n"; + System.out.println("Hello from\n" + logo); + System.out.println("Hello! I'm Duke"); + taskList.list(); + System.out.println("What can I do for you?"); + } + + public static void horizontalLine() { + System.out.println("------------------------------------"); + } +}