diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..7951f3a6c4 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,398 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..135ea49ee0 --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 8077118ebe..a8fe4fc3eb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,28 +2,98 @@ ## Features -### Feature-ABC +### Adding a ToDo task: `todo` -Description of the feature. +Adds a ToDo task to the task list. -### Feature-XYZ +Format: `todo TASK_DESCRIPTION` -Description of the feature. +Example: `todo read book` -## Usage +### Adding a Deadline task: `deadline` -### `Keyword` - Describe action +Adds a Deadline task to the task list. -Describe the action and its outcome. +Format: `deadline TASK_DESCRIPTION /by DATE` -Example of usage: +Example: `deadline return book /by 10/02/2024 1100` -`keyword (optional arguments)` +See [dateformat](https://github.com/tehkokhoe/ip/tree/master/docs#viewing-accepted-date-formats-dateformat) for accepted formats for date input. + +### Adding an Event task: `event` + +Adds an Event task to the task list. + +Format: `event TASK_DESCRIPTION /at START_DATE[-END_DATE]` + +Example: +- `event book fair /at 09/11/2022 1200-1300` +- `event lunch date /at 07/08/2022 1300` +- `event dinner date /at 04/12/2022` + +See [dateformat](https://github.com/tehkokhoe/ip/tree/master/docs#viewing-accepted-date-formats-dateformat) for accepted formats for date input. + +### Viewing your task list: `list` + +Displays your task list. + +### Marking a task as done: `mark` + +Marks a task as done in your task list. + +format: `mark INDEX` + +Example: `mark 1` + +Expected outcome: +``` +Nice! I've marked this task as done: + [T][X] read book +``` + +### Marking a task as not done: `unmark` + +Marks a task as not done in your task list. + +format: `unmark INDEX` + +Example: `unmark 2` Expected outcome: +``` +OK, I've marked this task as not done yet: + [T][ ] eat fish +``` + +### Deleting a task: `delete` + +Deletes a task in your task list. -Description of the outcome. +format: `delete INDEX` +Example: `delete 3` + +Expected outcome: ``` -expected output +Noted. I've removed this task: + [T][ ] run +Now you have 2 task(s) in the list ``` + +### Finding tasks with specific keyword: `find` + +Finds tasks that contains a keyword in the list. + +format: `find KEYWORD` + +Example: `find book` + +Expected outcome: +``` +Here are the matching tasks in your list: + 1. [T][X] read book +``` + +### Viewing accepted date formats: `dateformat` + +Displays a list of available date formats for input. diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..eb18bb2737 Binary files /dev/null and b/docs/Ui.png differ diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000000..3397c9a492 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-architect \ 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 5d313334cc..0000000000 --- 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/duke/Command.java b/src/main/java/duke/Command.java new file mode 100644 index 0000000000..226b907397 --- /dev/null +++ b/src/main/java/duke/Command.java @@ -0,0 +1,58 @@ +package duke; + +public enum Command { + BYE { + public boolean isRunning() { + return false; + } + }, + LIST { + public boolean isRunning() { + return true; + } + }, + MARK { + public boolean isRunning() { + return true; + } + }, + UNMARK { + public boolean isRunning() { + return true; + } + }, + TODO { + public boolean isRunning() { + return true; + } + }, + DEADLINE { + public boolean isRunning() { + return true; + } + }, + EVENT { + public boolean isRunning() { + return true; + } + }, + DELETE { + public boolean isRunning() { + return true; + } + }, + DATEFORMAT { + public boolean isRunning() { + return true; + } + }, + FIND { + public boolean isRunning() { + return true; + } + }; + + public abstract boolean isRunning(); +} + + diff --git a/src/main/java/duke/Deadline.java b/src/main/java/duke/Deadline.java new file mode 100644 index 0000000000..7b630458e9 --- /dev/null +++ b/src/main/java/duke/Deadline.java @@ -0,0 +1,56 @@ +package duke; + +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.DateTimeFormatterBuilder; +import org.joda.time.format.DateTimeParser; + +public class Deadline extends Task { + private String byString; + private DateTime byDate; + + /** + * Constructs a {@link Task} that has a date associated to it. The date + * associated is normally the date that the {@link Task} should be finished. + * + * @param task the description of the task. + * @param byString the due date of the task. + */ + public Deadline(String task, String byString) { + super(task); + this.byString = byString; + + try { + DateTimeParser[] dateParsers = { + DateTimeFormat.forPattern("d/MM/yyyy").getParser(), + DateTimeFormat.forPattern("yyyy-MM-dd").getParser(), + DateTimeFormat.forPattern("HHmm").getParser(), + DateTimeFormat.forPattern("d/MM/yyyy HHmm").getParser(), + DateTimeFormat.forPattern("yyyy-MM-dd HHmm").getParser(), + }; + DateTimeFormatter formatter = new DateTimeFormatterBuilder().append(null, dateParsers).toFormatter(); + DateTime date = formatter.parseDateTime(byString); + this.byDate = date; + } catch (UnsupportedOperationException e) { + this.byDate = null; + } catch (IllegalArgumentException e) { + this.byDate = null; + } + } + + @Override + public String toString() { + if (this.byDate == null) { + return "[D]" + super.toString() + " (by: " + byString + ")"; + } else { + String formattedDate = DateTimeFormat.forPattern("MMM dd yyyy h:mm a").print(byDate); + return "[D]" + super.toString() + " (by: " + formattedDate + ")"; + } + } + + @Override + public String toRecord() { + return "D | " + super.toRecord() + " | " + byString; + } +} diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java new file mode 100644 index 0000000000..eb10417f49 --- /dev/null +++ b/src/main/java/duke/Duke.java @@ -0,0 +1,146 @@ +package duke; + +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.layout.VBox; + +public class Duke { + private ScrollPane scrollPane; + private VBox dialogContainer; + private TextField userInput; + private Button sendButton; + private Scene scene; + private Image user = new Image(this.getClass().getResourceAsStream("/images/DaUser.png")); + private Image duke = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png")); + private UI ui; + private Storage storage; + private TaskList tasks; + + /** + * Constructs an instance of the main program, by setting up the + * {@link UI}, {@link Storage}, and loads the existing {@link TaskList} + * or creates a new {@link TaskList} if one does not already exist. + */ + public Duke() { + ui = new UI(); + storage = new Storage(); + try { + tasks = new TaskList(storage.load()); + } catch (DukeException e) { + ui.showLoadingError(e.getMessage()); + tasks = new TaskList(); + } + } + + /** + * Displays start screen. Runs program and keeps the program running until + * user exits. Accepts user input while running. + */ + public void run() { + ui.startScreen(); + boolean isRunning = true; + while (isRunning) { + String[] inputs = Parser.parseInput(ui); + UI.startLine(); + + try { + Command cmd = Parser.parseCommand(inputs); + this.execute(cmd, inputs); + isRunning = cmd.isRunning(); + } catch (IllegalArgumentException e) { + ui.showError(UI.getIndent() + "Invalid Command: " + inputs[0]); + } + + UI.endLine(); + } + } + + public static void main(String[] args) { + new Duke().run(); + } + + /** + * Executes {@link Command} with inputs given and returns a response. + * + * @param cmd one of the commands given in the enum {@link Command}. + * @param inputs the user input the has been split into command and description. + * @return the response after executing command. + * @see Command + */ + public String execute(Command cmd, String[] inputs) { + try { + String result = ""; + switch (cmd) { + case BYE: + ui.byeDisplay(); + assert result == ""; + break; + case LIST: + result = tasks.list(); + assert result != ""; + break; + case MARK: + result = tasks.mark(inputs); + storage.save(tasks.getTasks()); + assert result != ""; + break; + case UNMARK: + result = tasks.unmark(inputs); + storage.save(tasks.getTasks()); + assert result != ""; + break; + case TODO: + result = tasks.addToDo(inputs); + storage.save(tasks.getTasks()); + assert result != ""; + break; + case DEADLINE: + result = tasks.addDeadline(inputs); + storage.save(tasks.getTasks()); + assert result != ""; + break; + case EVENT: + result = tasks.addEvent(inputs); + storage.save(tasks.getTasks()); + assert result != ""; + break; + case DELETE: + result = tasks.delete(inputs); + storage.save(tasks.getTasks()); + assert result != ""; + break; + case DATEFORMAT: + result = ui.showDateFormats(); + assert result != ""; + break; + case FIND: + result = tasks.find(inputs); + assert result != ""; + break; + default: + throw new IllegalStateException(UI.getIndent() + "Unexpected value: " + cmd); + } + return result; + } catch (NumberFormatException e) { + return ui.showError(UI.getIndent() + "☹ OOPS!!! Task number given is not suitable"); + } catch (DukeException | IllegalStateException e) { + return ui.showError(e.getMessage()); + } catch (IllegalArgumentException e) { + return ui.showError(UI.getIndent() + "Invalid command: " + cmd); + } + } + + public String getResponse(String input) { + String[] inputs = Parser.parseGuiInput(input); + try { + Command cmd = Parser.parseCommand(inputs); + assert cmd == Command.valueOf(inputs[0]); + return this.execute(cmd, inputs); + } catch (IllegalArgumentException e) { + return ui.showError(UI.getIndent() + "Invalid Command: " + inputs[0]); + } + } +} diff --git a/src/main/java/duke/DukeDialogBox.java b/src/main/java/duke/DukeDialogBox.java new file mode 100644 index 0000000000..a6289f18b8 --- /dev/null +++ b/src/main/java/duke/DukeDialogBox.java @@ -0,0 +1,54 @@ +package duke; + +import java.io.IOException; +import java.util.Collections; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; + +public class DukeDialogBox extends HBox { + @FXML + private Label dialog; + @FXML + private ImageView displayPicture; + + /** + * An example of a custom control using FXML. + * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label + * containing text from the speaker. + */ + public DukeDialogBox(String text, Image img) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DukeDialogBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + dialog.setText(text); + displayPicture.setImage(img); + } + + private void flip() { + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + Collections.reverse(tmp); + getChildren().setAll(tmp); + setAlignment(Pos.TOP_LEFT); + } + + public static DukeDialogBox getDukeDialog(String text, Image img) { + var db = new DukeDialogBox(text, img); + db.flip(); + return db; + } +} diff --git a/src/main/java/duke/DukeException.java b/src/main/java/duke/DukeException.java new file mode 100644 index 0000000000..3fcd0f5ea8 --- /dev/null +++ b/src/main/java/duke/DukeException.java @@ -0,0 +1,7 @@ +package duke; + +public class DukeException extends Exception { + public DukeException(String message) { + super(message); + } +} diff --git a/src/main/java/duke/Event.java b/src/main/java/duke/Event.java new file mode 100644 index 0000000000..428db28a19 --- /dev/null +++ b/src/main/java/duke/Event.java @@ -0,0 +1,78 @@ +package duke; + +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.DateTimeFormatterBuilder; +import org.joda.time.format.DateTimeParser; + +public class Event extends Task { + private String atString; + private DateTime[] atDate; + + /** + * Constructs a {@link Task} that have dates associated to it. The date + * associated is normally the date that the {@link Task} occurs and ends. + * + * @param task the description of the task. + * @param atString the start date and end date of the task. + */ + public Event(String task, String atString) { + super(task); + this.atString = atString; + String[] arr = atString.split("\\s*-\\s*", 2); + + try { + DateTimeParser[] dateParsers = { + DateTimeFormat.forPattern("d/MM/yyyy").getParser(), + DateTimeFormat.forPattern("yyyy-MM-dd").getParser(), + DateTimeFormat.forPattern("HHmm").getParser(), + DateTimeFormat.forPattern("d/MM/yyyy HHmm").getParser(), + DateTimeFormat.forPattern("yyyy-MM-dd HHmm").getParser(), + }; + DateTimeFormatter formatter = new DateTimeFormatterBuilder().append(null, dateParsers).toFormatter(); + atDate = new DateTime[2]; + + for (int i = 0; i < 2; i++) { + DateTime date = formatter.parseDateTime(arr[i]); + this.atDate[i] = date; + } + } catch (UnsupportedOperationException e) { + this.atDate[0] = null; + this.atDate[1] = null; + } catch (IllegalArgumentException e) { + this.atDate[0] = null; + this.atDate[1] = null; + } + } + + @Override + public String toString() { + String formattedDate = ""; + if (this.atDate[0] == null && this.atDate[1] == null) { + return "[E]" + super.toString() + " (at: " + atString + ")"; + } else if (this.atDate[0] != null && this.atDate[1] != null) { + if (this.atDate[1].isAfter(this.atDate[0])) { + formattedDate = DateTimeFormat.forPattern("MMM dd yyyy h:mm a").print(atDate[0]) + + " - " + DateTimeFormat.forPattern("MMM dd yyyy h:mm a").print(atDate[1]); + } else { + formattedDate = DateTimeFormat.forPattern("MMM dd yyyy h:mm a").print(atDate[0]) + + " - " + DateTimeFormat.forPattern("h:mm a").print(atDate[1]); + } + } else if (this.atDate[0] == null) { + String[] arr = atString.split("\\s+-\\s+", 2); + formattedDate = arr[0] + "-" + + DateTimeFormat.forPattern("MMM dd yyyy h:mm a").print(atDate[1]); + } else { + String[] arr = atString.split("\\s+-\\s+", 2); + formattedDate = DateTimeFormat.forPattern("MMM dd yyyy h:mm a").print(atDate[0]) + + "-" + arr[1]; + } + return "[E]" + super.toString() + " (at: " + formattedDate + ")"; + } + + @Override + public String toRecord() { + return "E | " + super.toRecord() + " | " + atString; + } +} diff --git a/src/main/java/duke/FastReader.java b/src/main/java/duke/FastReader.java new file mode 100644 index 0000000000..6fe42a1ebb --- /dev/null +++ b/src/main/java/duke/FastReader.java @@ -0,0 +1,55 @@ +package duke; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.StringTokenizer; + +public class FastReader { + private BufferedReader br; + private StringTokenizer st; + + /** + * Constructs a reader that uses {@link BufferedReader}. + * @author Rishabh Mahrsee, GeeksforGeeks + */ + public FastReader() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + String next() { + while (st == null || !st.hasMoreElements()) { + try { + st = new StringTokenizer(br.readLine()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + return st.nextToken(); + } + + int nextInt() { + return Integer.parseInt(next()); + } + + long nextLong() { + return Long.parseLong(next()); + } + + double nextDouble() { + return Double.parseDouble(next()); + } + + String nextLine() { + String str = ""; + + try { + str = br.readLine(); + } catch (IOException e) { + e.printStackTrace(); + } + + return str; + } +} diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java new file mode 100644 index 0000000000..e4ef6b4628 --- /dev/null +++ b/src/main/java/duke/Launcher.java @@ -0,0 +1,12 @@ +package duke; + +import javafx.application.Application; + +/** + * A launcher class to workaround classpath issues. + */ +public class Launcher { + public static void main(String[] args) { + Application.launch(Main.class, args); + } +} diff --git a/src/main/java/duke/Main.java b/src/main/java/duke/Main.java new file mode 100644 index 0000000000..355e04241d --- /dev/null +++ b/src/main/java/duke/Main.java @@ -0,0 +1,27 @@ +package duke; + +import java.io.IOException; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + +public class Main extends Application { + private Duke duke = new Duke(); + + @Override + public void start(Stage stage) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml")); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + stage.setScene(scene); + fxmlLoader.getController().setDuke(duke); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/duke/MainWindow.java b/src/main/java/duke/MainWindow.java new file mode 100644 index 0000000000..d6a93b5f33 --- /dev/null +++ b/src/main/java/duke/MainWindow.java @@ -0,0 +1,59 @@ +package duke; + +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; + +public class MainWindow extends AnchorPane { + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogContainer; + @FXML + private TextField userInput; + @FXML + private Button sendButton; + @FXML + private Pane user; + @FXML + private ImageView userPic; + @FXML + private Label userText; + + private Duke duke; + + private Image userImage = new Image(this.getClass().getResourceAsStream("/images/DaUser.png")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png")); + + /** + * Initializes the GUI. + */ + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + userPic.setImage(userImage); + } + + public void setDuke(Duke d) { + duke = d; + } + + @FXML + private void handleUserInput() { + String input = userInput.getText(); + String response = duke.getResponse(input); + userText.setText(input); + dialogContainer.getChildren().addAll( + DukeDialogBox.getDukeDialog(response, dukeImage) + ); + userInput.clear(); + } +} + diff --git a/src/main/java/duke/Parser.java b/src/main/java/duke/Parser.java new file mode 100644 index 0000000000..a3fdb78dec --- /dev/null +++ b/src/main/java/duke/Parser.java @@ -0,0 +1,224 @@ +package duke; + +import java.util.ArrayList; + +public class Parser { + /** + * Returns a command by detecting the command in the user input. + * + * @param inputs user input that has been seperated into command and description. + * @return the command from user input. + * @throws IllegalArgumentException If command is not found in enum {@link Command}. + * @see Command + */ + public static Command parseCommand(String[] inputs) throws IllegalArgumentException { + Command cmd = Command.valueOf(inputs[0].toUpperCase()); + return cmd; + } + + /** + * Returns index of list that should be marked. + * + * @param tasks the list of tasks saved. + * @param inputs user input that has been seperated into command and description. + * @return the index of the task in the list that should be marked. + * @throws DukeException If index is not given, index given is not an integer or index > size + * of task list. + * @see Task + */ + public static int parseMarkIndex(ArrayList tasks, String[] inputs) throws DukeException { + if (inputs.length < 2) { + throw new DukeException(UI.getIndent() + "☹ OOPS!!! I don't know what to mark"); + } + + int markIndex = Integer.parseInt(inputs[1]); + + if (markIndex > tasks.size()) { + throw new DukeException(UI.getIndent() + "☹ OOPS!!! I don't see your task"); + } + + return markIndex; + } + + /** + * Returns index of list that should be unmarked. + * + * @param tasks the list of tasks saved. + * @param inputs user input that has been seperated into command and description. + * @return the index of the task in the list that should be unmarked. + * @throws DukeException If index is not given, index given is not an integer or index > size + * of task list. + * @see Task + */ + public static int parseUnmarkIndex(ArrayList tasks, String[] inputs) throws DukeException { + if (inputs.length < 2) { + throw new DukeException(UI.getIndent() + "☹ OOPS!!! I don't know what to mark"); + } + + int unmarkIndex = Integer.parseInt(inputs[1]); + + if (unmarkIndex > tasks.size()) { + throw new DukeException(UI.getIndent() + "☹ OOPS!!! I don't see your task"); + } + + return unmarkIndex; + } + + /** + * Check if description of {@link ToDo} is empty. + * + * @param inputs user input that has been seperated into command and description. + * @throws DukeException If input length < 2. + */ + public static void checkToDoDescription(String[] inputs) throws DukeException { + if (inputs.length < 2) { + throw new DukeException(UI.getIndent() + "☹ OOPS!!! The description of a todo cannot be empty"); + } + } + + /** + * Check if description of {@link Deadline} is empty. + * + * @param inputs user input that has been seperated into command and description. + * @throws DukeException If input length < 2. + */ + public static void checkDeadlineDescription(String[] inputs) throws DukeException { + if (inputs.length < 2) { + throw new DukeException(UI.getIndent() + "☹ OOPS!!! The description of a deadline cannot be empty"); + } + } + + /** + * Returns a {@link String} array that separates the task description and the date. + * + * @param inputs user input that has been seperated into command and description. + * @return the description of the task seperated into task description and the date. + * @throws DukeException If date is not given or {@link Deadline} input is not in the right + * format. + */ + public static String[] splitDeadlineDate(String[] inputs) throws DukeException { + String[] splitByDate = inputs[1].split(" /by "); + + if (splitByDate.length < 2) { + throw new DukeException(UI.getIndent() + "☹ OOPS!!! I don't know when your task is due"); + } + + return splitByDate; + } + + /** + * Check if description of {@link Event} is empty. + * + * @param inputs user input that has been seperated into command and description. + * @throws DukeException If input length < 2. + */ + public static void checkEventDescription(String[] inputs) throws DukeException { + if (inputs.length < 2) { + throw new DukeException(UI.getIndent() + "☹ OOPS!!! The description of an event cannot be empty"); + } + } + + /** + * Returns a {@link String} array that separates the task description and the dates. + * + * @param inputs user input that has been seperated into command and description. + * @return the description of the task seperated into task description and the dates. + * @throws DukeException If dates are not given or {@link Event} input is not in the + * right format. + */ + public static String[] splitEventDate(String[] inputs) throws DukeException { + String[] splitAtDate = inputs[1].split(" /at "); + + if (splitAtDate.length < 2) { + throw new DukeException(UI.getIndent() + "☹ OOPS!!! I don't know when your event happens"); + } + + return splitAtDate; + } + + /** + * Returns the index of the list to delete. + * + * @param inputs user input that has been seperated into command and description. + * @param tasks list of tasks saved. + * @return the index of the task in the list to delete. + * @throws DukeException If index is not given, index given is not an integer or index > size + * of task list. + */ + public static int parseDeleteIndex(String[] inputs, ArrayList tasks) throws DukeException { + if (inputs.length < 2) { + throw new DukeException(UI.getIndent() + "☹ OOPS!!! I don't know what to delete"); + } + + int removeNum = Integer.parseInt(inputs[1]); + + if (removeNum > tasks.size()) { + throw new DukeException(UI.getIndent() + "☹ OOPS!!! I don't see your task"); + } + + return removeNum; + } + + /** + * Returns a {@link String} array with the command and description seperated. + * + * @param ui the user interface that accepts user input. + * @return the array with command and description seperated. + */ + public static String[] parseInput(UI ui) { + String in = ui.read(); + String[] inputs = in.split(" ", 2); + return inputs; + } + + /** + * Returns a {@link String} array with the command and description seperated. + * + * @param input the user input. + * @return the array with command and description seperated. + */ + public static String[] parseGuiInput(String input) { + String[] inputs = input.split(" ", 2); + return inputs; + } + + /** + * Returns a Task object by parsing entry from saved file. + * + * @param entry a task string in the form saved in file. + * @return the Task object that represents the entry inserted. + */ + public static Task parseEntry(String entry) { + String[] display = entry.split(" \\| "); + Task task = new Task(); + + if (display[0].equals("T")) { + task = new ToDo(display[2]); + } else if (display[0].equals("D")) { + task = new Deadline(display[2], display[3]); + } else if (display[0].equals("E")) { + task = new Event(display[2], display[3]); + } + + if (display[1].equals("1")) { + task.setDone(); + } + + return task; + } + + /** + * Returns the keyword in the user input. + * + * @param input the user input. + * @return the keyword associated with find command. + * @throws DukeException If keyword is not given. + */ + public static String parseKeyword(String[] input) throws DukeException { + if (input.length < 2) { + throw new DukeException(UI.getIndent() + "☹ OOPS!!! I don't know what keyword to look for"); + } + + return input[1]; + } +} diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/Storage.java new file mode 100644 index 0000000000..d10689fca5 --- /dev/null +++ b/src/main/java/duke/Storage.java @@ -0,0 +1,89 @@ +package duke; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; + +public class Storage { + public Storage() { + } + + /** + * Saves the task list in a file. + * + * @param tasks list of tasks saved. + */ + public void save(ArrayList tasks) { + String working = System.getProperty("user.dir"); + Path path = Paths.get(working, "data"); + path.toFile().mkdirs(); + File file = new File(path + "/duke.txt"); + + try { + file.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + } + + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(file)); + StringBuilder output = new StringBuilder(); + + for (int i = 0; i < tasks.size(); i++) { + output.append(tasks.get(i)).append("\n"); + } + + writer.write(output.toString()); + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Returns data out of saved file as an {@link ArrayList}. + * + * @return the list of entries retrieved from save file. + * @throws DukeException If file could not be used or created. + */ + public ArrayList load() throws DukeException { + String working = System.getProperty("user.dir"); + Path path = Paths.get(working, "data"); + path.toFile().mkdirs(); + File file = new File(path + "/duke.txt"); + + try { + file.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + throw new DukeException("Saved task list could not be loaded"); + } + + ArrayList input = new ArrayList<>(); + + try { + BufferedReader reader = new BufferedReader(new FileReader(file)); + String entry = reader.readLine(); + + while (entry != null) { + input.add(entry); + entry = reader.readLine(); + } + + } catch (FileNotFoundException e) { + e.printStackTrace(); + throw new DukeException("Saved task list could not be loaded"); + } catch (IOException e) { + e.printStackTrace(); + } + + return input; + } +} diff --git a/src/main/java/duke/Task.java b/src/main/java/duke/Task.java new file mode 100644 index 0000000000..522062d9dc --- /dev/null +++ b/src/main/java/duke/Task.java @@ -0,0 +1,79 @@ +package duke; + +public class Task { + private String task; + private boolean isDone; + + public Task() { + this.isDone = false; + } + + /** + * Constructs a task. + *

+ * Task is initialized as not done. + * + * @param task the task description. + * @return the index of the task in the list to delete. + * @throws DukeException If index is not given, index given is not an integer or index > size + * of task list. + */ + public Task(String task) { + this.task = task; + this.isDone = false; + } + + public String getTask() { + return task; + } + + public void setDone() { + this.isDone = true; + } + + public void setUndone() { + this.isDone = false; + } + + @Override + public int hashCode() { + return task.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other instanceof Task) { + Task toCompare = (Task) other; + return this.task.equals(toCompare.getTask()); + } + return false; + } + + @Override + public String toString() { + String temp; + if (this.isDone) { + temp = "X"; + } else { + temp = " "; + } + return "[" + temp + "] " + this.task; + } + + /** + * Returns a {@link String} representing a {@link Task} in the form convenient for record. + * + * @return the text representing the task to record in save file. + */ + public String toRecord() { + int temp; + + if (this.isDone) { + temp = 1; + } else { + temp = 0; + } + + return temp + " | " + this.task; + } +} diff --git a/src/main/java/duke/TaskList.java b/src/main/java/duke/TaskList.java new file mode 100644 index 0000000000..9dcbfe8ebb --- /dev/null +++ b/src/main/java/duke/TaskList.java @@ -0,0 +1,160 @@ +package duke; + +import java.util.ArrayList; + +public class TaskList { + private ArrayList tasks; + private ArrayList display; + + /** + * Constructs a list of tasks and converts entries in save file to a list of {@link Task} + * objects. + * + * @param tasks list of tasks saved. + */ + public TaskList(ArrayList tasks) { + this.tasks = tasks; + this.display = new ArrayList<>(); + + for (int i = 0; i < tasks.size(); i++) { + display.add(Parser.parseEntry(tasks.get(i))); + } + } + + public TaskList() { + this.tasks = new ArrayList(); + } + + public ArrayList getTasks() { + return tasks; + } + + public String list() { + return UI.listDisplay(display); + } + + /** + * Marks a task, and returns a response. + * + * @param inputs the user input that has been seperated into command and index. + * @return the response after marking a task. + * @throws DukeException If index is not given, index given is not an integer or + * index > size of task list. + */ + public String mark(String[] inputs) throws DukeException { + int markIndex = Parser.parseMarkIndex(display, inputs); + display.get(markIndex - 1).setDone(); + tasks.set(markIndex - 1, display.get(markIndex - 1).toRecord()); + return UI.markDisplay(display.get(markIndex - 1)); + } + + /** + * Unmarks a task and returns a response. + * + * @param inputs the user input that has been seperated into command and index. + * @return the response after unmarking a task. + * @throws DukeException If index is not given, index given is not an integer or + * index > size of task list. + */ + public String unmark(String[] inputs) throws DukeException { + int unmarkIndex = Parser.parseUnmarkIndex(display, inputs); + display.get(unmarkIndex - 1).setUndone(); + tasks.set(unmarkIndex - 1, display.get(unmarkIndex - 1).toRecord()); + return UI.unmarkDisplay(display.get(unmarkIndex - 1)); + } + + /** + * Adds a {@link ToDo} {@link Task} and returns a response. + * + * @param inputs the user input that has been seperated into command and description. + * @return the response after adding the to do task to the list. + * @throws DukeException If input length < 2. + */ + public String addToDo(String[] inputs) throws DukeException { + Parser.checkToDoDescription(inputs); + Task toDo = new ToDo(inputs[1]); + if (this.isDuplicate(toDo)) { + return UI.duplicateDisplay(toDo); + } + display.add(toDo); + tasks.add(toDo.toRecord()); + return UI.toDoDisplay(toDo, display); + } + + /** + * Adds a {@link Deadline} {@link Task} and returns a response. + * + * @param inputs the user input that has been seperated into command and description. + * @return the response after adding the deadline task to the list. + * @throws DukeException If input length < 2. + */ + public String addDeadline(String[] inputs) throws DukeException { + Parser.checkDeadlineDescription(inputs); + String[] splitByDate = Parser.splitDeadlineDate(inputs); + String byDate = splitByDate[1]; + Task deadline = new Deadline(splitByDate[0], byDate); + if (this.isDuplicate(deadline)) { + return UI.duplicateDisplay(deadline); + } + display.add(deadline); + tasks.add(deadline.toRecord()); + return UI.deadlineDisplay(deadline, display); + } + + /** + * Adds a {@link Event} {@link Task} and returns a response. + * + * @param inputs user input that has been seperated into command and description. + * @return the response after adding the event task to the list. + * @throws DukeException If input length < 2. + */ + public String addEvent(String[] inputs) throws DukeException { + Parser.checkEventDescription(inputs); + String[] splitAtDate = Parser.splitEventDate(inputs); + String atDate = splitAtDate[1]; + Task event = new Event(splitAtDate[0], atDate); + if (this.isDuplicate(event)) { + return UI.duplicateDisplay(event); + } + display.add(event); + tasks.add(event.toRecord()); + return UI.eventDisplay(event, display); + } + + /** + * Delete a task from the task list and returns a response. + * + * @param inputs user input that has been seperated into command and index. + * @return the response after deleting a task from the list. + * @throws DukeException If index is not given, index given is not an integer or + * index > size of task list. + */ + public String delete(String[] inputs) throws DukeException { + int deleteIndex = Parser.parseDeleteIndex(inputs, display); + Task deletedTask = display.remove(deleteIndex - 1); + tasks.remove(deleteIndex - 1); + return UI.deleteDisplay(deletedTask, display); + } + + /** + * Find a task from the task list and return a response + * + * @param inputs user input that has been seperated into command and keyword. + * @return the response after finding a task from the list. + * @throws DukeException If keyword is not given. + */ + public String find(String[] inputs) throws DukeException { + String keyword = Parser.parseKeyword(inputs); + ArrayList foundTasks = new ArrayList<>(); + for (int i = 0; i < tasks.size(); i++) { + if (tasks.get(i).contains(keyword)) { + foundTasks.add(display.get(i)); + } + } + return UI.findDisplay(foundTasks); + } + + public boolean isDuplicate(Task task) { + return display.contains(task); + } +} diff --git a/src/main/java/duke/ToDo.java b/src/main/java/duke/ToDo.java new file mode 100644 index 0000000000..dde24cacfb --- /dev/null +++ b/src/main/java/duke/ToDo.java @@ -0,0 +1,17 @@ +package duke; + +public class ToDo extends Task { + public ToDo(String task) { + super(task); + } + + @Override + public String toString() { + return "[T]" + super.toString(); + } + + @Override + public String toRecord() { + return "T | " + super.toRecord(); + } +} diff --git a/src/main/java/duke/UI.java b/src/main/java/duke/UI.java new file mode 100644 index 0000000000..bf50368470 --- /dev/null +++ b/src/main/java/duke/UI.java @@ -0,0 +1,250 @@ +package duke; + +import java.util.ArrayList; + +public class UI { + private static final String[] dateFormat = {"d/MM/yyyy", "yyyy-MM-dd", "HHmm", "d/MM/yyyy HHmm", "yyyy-MM-dd HHmm"}; + private static final String INDENT = " "; + private static final String SPACE = " "; + private FastReader fr; + + public UI() { + fr = new FastReader(); + } + + /** + * Prints the starting screen + */ + public void startScreen() { + String logo = " ____ _ \n" + + "| _ \\ _ _| | _____ \n" + + "| | | | | | | |/ / _ \\\n" + + "| |_| | |_| | < __/\n" + + "|____/ \\__,_|_|\\_\\___|\n"; + System.out.println("Hello from\n" + logo); + System.out.println(INDENT + "____________________________________________________________"); + System.out.println(INDENT + "Hello! I'm Duke"); + System.out.println(INDENT + "What can I do for you?"); + System.out.println(INDENT + "____________________________________________________________"); + } + + public String read() { + return fr.nextLine(); + } + + public static void startLine() { + System.out.println(INDENT + "____________________________________________________________"); + } + + public static void endLine() { + System.out.println(INDENT + "____________________________________________________________"); + } + + public void byeDisplay() { + System.out.println(INDENT + "Bye. Hope to see you again soon!"); + } + + /** + * Returns the list of tasks. + * + * @param tasks the list of tasks. + * @return the response of list command. + */ + public static String listDisplay(ArrayList tasks) { + String response = ""; + for (int i = 0; i < tasks.size(); i++) { + if (i == 0) { + response += "Here are the tasks in your list:\n"; + System.out.println(INDENT + "Here are the tasks in your list:"); + } + + response += String.format(SPACE + "%d. %s\n", i + 1, tasks.get(i)); + System.out.printf(INDENT + SPACE + "%d. %s\n", i + 1, tasks.get(i)); + } + + if (tasks.size() == 0) { + response += "There's nothing in your list\n"; + System.out.println(INDENT + "There's nothing in your list"); + } + + return response; + } + + /** + * Returns marked task. + * + * @param task the marked task. + * @return the response to mark command. + */ + public static String markDisplay(Task task) { + String response = ""; + response += "Nice! I've marked this task as done:\n"; + System.out.println(INDENT + "Nice! I've marked this task as done:"); + response += String.format(SPACE + "%s\n", task); + System.out.println(INDENT + SPACE + task); + return response; + } + + /** + * Returns unmarked task. + * + * @param task the unmarked task. + * @return the response to unmark command. + */ + public static String unmarkDisplay(Task task) { + String response = ""; + response += "OK, I've marked this task as not done yet:\n"; + System.out.println(INDENT + "OK, I've marked this task as not done yet:"); + response += String.format(SPACE + "%s\n", task); + System.out.println(INDENT + SPACE + task); + return response; + } + + public static String getIndent() { + return INDENT; + } + + public static String getSpace() { + return SPACE; + } + + /** + * Returns added {@link ToDo} {@link Task}. + * + * @param toDo the added toDo task. + * @param tasks the list of tasks. + * @return the response to todo command. + */ + public static String toDoDisplay(Task toDo, ArrayList tasks) { + String response = ""; + response += "Got it. I've added this task:\n"; + System.out.println(INDENT + "Got it. I've added this task:"); + response += String.format(SPACE + "%s\n", toDo); + System.out.println(INDENT + SPACE + toDo); + response += String.format("Now you have %d task(s) in the list\n", tasks.size()); + System.out.printf(INDENT + "Now you have %d task(s) in the list\n", tasks.size()); + return response; + } + + /** + * Returns added {@link Deadline} {@link Task}. + * + * @param deadline the added deadline task. + * @param tasks the list of tasks. + * @return the response to deadline command. + */ + public static String deadlineDisplay(Task deadline, ArrayList tasks) { + String response = ""; + response += "Got it. I've added this task:\n"; + System.out.println(INDENT + "Got it. I've added this task:"); + response += String.format(SPACE + "%s\n", deadline); + System.out.println(INDENT + SPACE + deadline); + response += String.format("Now you have %d task(s) in the list\n", tasks.size()); + System.out.printf(INDENT + "Now you have %d task(s) in the list\n", tasks.size()); + return response; + } + + /** + * Returns added {@link Event} {@link Task}. + * + * @param event the added event task. + * @param tasks the list of tasks. + * @return the response to event command. + */ + public static String eventDisplay(Task event, ArrayList tasks) { + String response = ""; + response += "Got it. I've added this task:\n"; + System.out.println(INDENT + "Got it. I've added this task:"); + response += String.format(SPACE + "%s\n", event); + System.out.println(INDENT + SPACE + event); + response += String.format("Now you have %d task(s) in the list\n", tasks.size()); + System.out.printf(INDENT + "Now you have %d task(s) in the list\n", tasks.size()); + return response; + } + + /** + * Returns deleted task. + * + * @param deletedTask the deleted task. + * @param tasks the list of tasks. + * @return the response to delete command. + */ + public static String deleteDisplay(Task deletedTask, ArrayList tasks) { + String response = ""; + response += "Noted. I've removed this task:\n"; + System.out.println(INDENT + "Noted. I've removed this task:"); + response += String.format(SPACE + "%s\n", deletedTask); + System.out.println(INDENT + SPACE + deletedTask); + response += String.format("Now you have %d task(s) in the list\n", tasks.size()); + System.out.printf(INDENT + "Now you have %d task(s) in the list\n", tasks.size()); + return response; + } + + /** + * Returns loading error message. + * + * @param e the error message. + * @return the loading error message. + */ + public String showLoadingError(String e) { + System.out.println(e); + return e; + } + + /** + * Returns error message. + * + * @param e the error message. + * @return the error message. + */ + public String showError(String e) { + System.out.println(e); + return e; + } + + /** + * Returns a list of viable date formats for user input. + * + * @return the response for dateformat command. + */ + public String showDateFormats() { + String response = ""; + + for (String s : dateFormat) { + response += s + "\n"; + System.out.println(s); + } + + return response; + } + + /** + * Return found tasks. + * + * @param found the list of found tasks containing keyword. + * @return the response to find command. + */ + public static String findDisplay(ArrayList found) { + String response = ""; + if (found.size() < 1) { + System.out.println(INDENT + "I couldn't find any task with that keyword"); + return "I couldn't find any task with that keyword"; + } + + response += "Here are the matching tasks in your list:\n"; + System.out.println(INDENT + "Here are the matching tasks in your list:"); + int i = 1; + + for (Task task : found) { + response += String.format(SPACE + "%d. %s\n", i, task); + System.out.printf(INDENT + SPACE + "%d. %s\n", i, task); + i += 1; + } + + return response; + } + + public static String duplicateDisplay(Task task) { + return String.format("You already have task\n %s\non your list", task); + } +} diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png new file mode 100644 index 0000000000..d893658717 Binary files /dev/null and b/src/main/resources/images/DaDuke.png differ diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png new file mode 100644 index 0000000000..3c82f45461 Binary files /dev/null and b/src/main/resources/images/DaUser.png differ diff --git a/src/main/resources/view/DukeDialogBox.fxml b/src/main/resources/view/DukeDialogBox.fxml new file mode 100644 index 0000000000..3d5055f8ac --- /dev/null +++ b/src/main/resources/view/DukeDialogBox.fxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..0c05e039d7 --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + +