+ * The tasks are saved automatically after every action. + * This ensures that all changes are preserved and will be available + * the next time the program is run. + *
+ * @author Kok Bo Chang + */ +public class Lawrence { + private static final String NAME = "Lawrence"; + private static final Path SAVE_LOCATION = Paths.get(".", "data", "tasks.txt"); + + private final TaskFileManager manager; + private TaskList tasks; + private final UserInterface ui; + + /** + * Default constructor. + */ + public Lawrence() { + ui = new UserInterface(NAME); + manager = new TaskFileManager(SAVE_LOCATION); + try { + Task[] existingTasks = manager.readTasksFromFile(); + tasks = new TaskList(existingTasks); + } catch (IOException e) { + // initialise with no tasks instead + tasks = new TaskList(); + } + + assert tasks != null; // the task object will always be initialised + } + + /** + * Initialises and runs the Lawrence chatbot. + * + * @param args optional startup arguments + */ + public static void main(String[] args) { + Lawrence lawrence = new Lawrence(); + lawrence.run(); + } + + /** + * Runs the chatbot to start listening for user commands + * in the console. + */ + public void run() { + ui.greetUser(); + + Scanner sc = new Scanner(System.in); + String userInput; + boolean shouldContinue = true; + while (shouldContinue) { + userInput = sc.nextLine(); + try { + Command c = CommandParser.createCommand(userInput); + c.execute(tasks, manager, ui); + shouldContinue = c.shouldContinue(); + } catch (IllegalArgumentException | IllegalStateException e) { + ui.showMessage(String.format("%s Please try again.", e.getMessage())); + } + } + + assert shouldContinue == false; // program should only exit once shouldContinue is false + } + + /** + * Returns the text response of the bot after parsing the input string and executing the relevant command. + * + * @param input the input string containing instructions on what command to run + * @return a {@link Response} object containing details on the actions the bot took + */ + public Response getResponse(String input) { + try { + Command c = CommandParser.createCommand(input); + String message = c.execute(tasks, manager, ui); + return new Response(c.getType(), message, c.shouldContinue()); + } catch (IllegalArgumentException | IllegalStateException e) { + String message = String.format("%s Please try again.", e.getMessage()); + return new Response(CommandType.INVALID, message, true); + } + } + + public String getWelcomeMessage() { + return String.format("Hello! I'm %s and I'm here to establish another GST hike.%n" + + "What can I do for you?", NAME); + } +} diff --git a/src/main/java/lawrence/app/Response.java b/src/main/java/lawrence/app/Response.java new file mode 100644 index 0000000000..264c1fd571 --- /dev/null +++ b/src/main/java/lawrence/app/Response.java @@ -0,0 +1,9 @@ +package lawrence.app; + +import lawrence.command.CommandType; + +/** + * Contains information about the action taken by the bot after issuing a command to it. + */ +public record Response(CommandType commandType, String message, boolean shouldContinue) { +} diff --git a/src/main/java/lawrence/command/AddTaskCommand.java b/src/main/java/lawrence/command/AddTaskCommand.java new file mode 100644 index 0000000000..fa99f47ef7 --- /dev/null +++ b/src/main/java/lawrence/command/AddTaskCommand.java @@ -0,0 +1,76 @@ +package lawrence.command; + +import java.io.IOException; +import java.time.format.DateTimeParseException; + +import lawrence.database.TaskFileManager; +import lawrence.parser.InputSource; +import lawrence.parser.TaskParser; +import lawrence.task.Task; +import lawrence.task.TaskList; +import lawrence.ui.UserInterface; +import lawrence.utils.DateParser; + +/** + * Represents the user command to create new tasks. + */ +public class AddTaskCommand extends Command { + private final String input; + + /** + * Default constructor. + * + * @param input the user input associated with this command + */ + public AddTaskCommand(CommandType type, String input) { + super(type); + this.input = input; + } + + /** + * Creates the relevant task based on user input, then adds + * the new task into the {@link TaskList} and saves the + * tasks into a text file. + *+ * Displays the result of the execution to the user afterwards. + *
+ *+ * If information about the task to add is invalid, + * no new task will be created and the text file will not be updated. + *
+ * + * @param tasks a list of tasks the command may operate + * on + * @param manager a {@link TaskFileManager} instance that + * the command may use when saving changes + * made + * @param ui a {@link UserInterface} instance to display + * possible messages to the user + * @return a string representing the bot's response after execution of the command + */ + @Override + public String execute(TaskList tasks, TaskFileManager manager, UserInterface ui) { + try { + Task t = TaskParser.createTask(input, InputSource.USER); + tasks.addTask(t); + saveTasks(tasks, manager); + + // format components of message to display + int numberOfTasks = tasks.getSize(); + + assert numberOfTasks > 0; // successfully adding a task means tasks will always have a length of at least 1 + + String verb = numberOfTasks == 1 ? "is" : "are"; + String plural = numberOfTasks == 1 ? "" : "s"; + return String.format("Alright, added task:%n%s%nto the list.%n" + + "There %s currently %d task%s in the list.", t, verb, numberOfTasks, plural); + } catch (DateTimeParseException e) { + return String.format("Invalid date and/or time provided. DateTime should be in the format: %s", + DateParser.FORMAT_STRING_FOR_USER_INPUT); + } catch (IllegalArgumentException e) { + return e.getMessage(); + } catch (IOException e) { + return "Failed to save tasks to file. Please try again."; + } + } +} diff --git a/src/main/java/lawrence/command/Command.java b/src/main/java/lawrence/command/Command.java new file mode 100644 index 0000000000..da02933fb9 --- /dev/null +++ b/src/main/java/lawrence/command/Command.java @@ -0,0 +1,71 @@ +package lawrence.command; + +import java.io.IOException; + +import lawrence.database.TaskFileManager; +import lawrence.task.TaskList; +import lawrence.ui.UserInterface; + +/** + * Represents a general command that can be issued by the user. + *+ * The command is not processed until + * {@link #execute(TaskList, TaskFileManager, UserInterface)} + * is called. + *
+ */ +public abstract class Command { + private final CommandType type; + + protected Command(CommandType type) { + this.type = type; + } + /** + * Executes the specified user command. + * + * @param tasks a list of tasks the command may operate + * on + * @param manager a {@link TaskFileManager} instance that + * the command may use when saving changes + * made + * @param ui a {@link UserInterface} instance to display + * possible messages to the user + * @return a string representing the bot's response after execution of the command + */ + public abstract String execute(TaskList tasks, TaskFileManager manager, UserInterface ui); + + /** + * Returns a boolean indicating whether the program should + * continue running after this command. Defaults to true. + * + * @return a boolean indicating whether the program should + * continue running + */ + public boolean shouldContinue() { + return true; + } + + /** + * Saves the current tasks in memory to a text file. + *+ * This operation will overwrite the original text file entirely. + *
+ * + * @param tasks the list of tasks to be saved into the text file + * @param manager the {@link TaskFileManager} instance that + * can write to the text file + * @throws IOException if writing to the file is unsuccessful + */ + protected void saveTasks(TaskList tasks, TaskFileManager manager) throws IOException { + manager.saveTasksToFile(tasks.getTasks()); + } + + /** + * Returns the type of the current command. + * + * @return the command type + */ + public CommandType getType() { + return type; + } +} diff --git a/src/main/java/lawrence/command/CommandType.java b/src/main/java/lawrence/command/CommandType.java new file mode 100644 index 0000000000..175f7b8576 --- /dev/null +++ b/src/main/java/lawrence/command/CommandType.java @@ -0,0 +1,80 @@ +package lawrence.command; + +import java.util.ArrayList; + +/** + * Represents the different commands that can be issued by the user. + */ +public enum CommandType { + ADD_DEADLINE("deadline"), + ADD_EVENT("event"), + ADD_TODO("todo"), + DELETE("delete"), + DISPLAY("list"), + EXIT("bye"), + FIND_MATCHING("find"), + INVALID("invalid"), + MARK_COMPLETE("mark"), + MARK_INCOMPLETE("unmark"); + + private final String commandType; + + /** + * Default constructor. + *+ * The input string is converted into lowercase for greater input flexibility. + *
+ * + * @param type the string containing an enum value + */ + CommandType(String type) { + this.commandType = type.toLowerCase(); + } + + /** + * Converts a text string into its relevant enum counterpart. Full string matching + * and partial string matching are both done. + *+ * If input does not match any known types, the {@link #INVALID} type + * is returned. + * IF the input matches multiple types, the {@link #INVALID} type is also returned. + *
+ * + * @param input the text containing an enum value + * @return an enum type matching the input + */ + public static CommandType fromString(String input) { + ArrayList+ * If information about the task to mark complete is invalid, the + * method does nothing. + *
+ * + * @param tasks a list of tasks the command may operate + * on + * @param manager a {@link TaskFileManager} instance that + * the command may use when saving changes + * made + * @param ui a {@link UserInterface} instance to display + * possible messages to the user + * @return a string representing the bot's response after execution of the command + */ + @Override + public String execute(TaskList tasks, TaskFileManager manager, UserInterface ui) { + String[] inputComponents = input.split(" ", 2); + if (inputComponents.length < 2) { + return "Please specify the task you want to mark as complete."; + } + + assert inputComponents.length == 2; + + String rawTaskNumber = inputComponents[1]; + try { + int taskNumber = Integer.parseInt(rawTaskNumber); + Task completeTask = tasks.completeTask(taskNumber); + saveTasks(tasks, manager); + return String.format("I've marked the task as complete:%n%s", completeTask); + } catch (NumberFormatException e) { + return "Please specify a number to select a task."; + } catch (IllegalArgumentException | IllegalStateException e) { + return String.format("%s Please try again.", e.getMessage()); + } catch (IOException e) { + return String.format("Failed to mark task %s as complete. Please try again later.", rawTaskNumber); + } + } +} diff --git a/src/main/java/lawrence/command/DeleteTaskCommand.java b/src/main/java/lawrence/command/DeleteTaskCommand.java new file mode 100644 index 0000000000..07383aae4f --- /dev/null +++ b/src/main/java/lawrence/command/DeleteTaskCommand.java @@ -0,0 +1,66 @@ +package lawrence.command; + +import java.io.IOException; + +import lawrence.database.TaskFileManager; +import lawrence.task.Task; +import lawrence.task.TaskList; +import lawrence.ui.UserInterface; + +/** + * Represents the user command to delete existing tasks from the list. + */ +public class DeleteTaskCommand extends Command { + private final String input; + + /** + * Default constructor. + * + * @param input the user input associated with this command + */ + public DeleteTaskCommand(CommandType type, String input) { + super(type); + this.input = input; + } + + /** + * Finds the specified task and deletes it from the list, then + * displays the new list after deletion to the user. + *+ * If information about the task to be deleted is invalid, + * the method does nothing. + *
+ * + * @param tasks a list of tasks the command may operate + * on + * @param manager a {@link TaskFileManager} instance that + * the command may use when saving changes + * made + * @param ui a {@link UserInterface} instance to display + * possible messages to the user + * @return a string representing the bot's response after execution of the command + */ + @Override + public String execute(TaskList tasks, TaskFileManager manager, UserInterface ui) { + String[] inputComponents = input.split(" ", 2); + if (inputComponents.length < 2) { + return "Please specify the task you want to delete."; + } + + String rawTaskNumber = inputComponents[1]; + try { + int taskNumber = Integer.parseInt(rawTaskNumber); + Task deletedTask = tasks.deleteTask(taskNumber); + saveTasks(tasks, manager); + + return String.format("Task:%n%s%nhas been deleted.", deletedTask); + } catch (NumberFormatException e) { + return "Please specify an integer to select a task."; + } catch (IllegalArgumentException | IllegalStateException e) { + return String.format("%s Please try again.", e.getMessage()); + } catch (IOException e) { + return String.format("Failed to delete task %s from the list. Please try again.", rawTaskNumber); + } + } + +} diff --git a/src/main/java/lawrence/command/DisplayTasksCommand.java b/src/main/java/lawrence/command/DisplayTasksCommand.java new file mode 100644 index 0000000000..d0972826a1 --- /dev/null +++ b/src/main/java/lawrence/command/DisplayTasksCommand.java @@ -0,0 +1,40 @@ +package lawrence.command; + +import lawrence.database.TaskFileManager; +import lawrence.task.TaskList; +import lawrence.ui.UserInterface; + +/** + * Represents the user command to display all existing tasks in the list. + */ +public class DisplayTasksCommand extends Command { + /** + * Default constructor. + */ + public DisplayTasksCommand(CommandType type) { + super(type); + } + + /** + * Displays all tasks present in the {@link TaskList} to the user. + * If no tasks exist, a different message is displayed. + * + * @param tasks a list of tasks the command may operate + * on + * @param manager a {@link TaskFileManager} instance that + * the command may use when saving changes + * made + * @param ui a {@link UserInterface} instance to display + * possible messages to the user + * @return a string representing the bot's response after execution of the command + */ + @Override + public String execute(TaskList tasks, TaskFileManager manager, UserInterface ui) { + if (tasks.getSize() < 1) { + return "You have no tasks at the moment."; + } + + assert tasks.getSize() >= 0; + return String.format("Here's your laundry list:%n%s", tasks); + } +} diff --git a/src/main/java/lawrence/command/ExitSessionCommand.java b/src/main/java/lawrence/command/ExitSessionCommand.java new file mode 100644 index 0000000000..b86d91164f --- /dev/null +++ b/src/main/java/lawrence/command/ExitSessionCommand.java @@ -0,0 +1,46 @@ +package lawrence.command; + +import lawrence.database.TaskFileManager; +import lawrence.task.TaskList; +import lawrence.ui.UserInterface; + +/** + * Represents the user command to exit the chatbot. + */ +public class ExitSessionCommand extends Command { + /** + * Default constructor. + */ + public ExitSessionCommand(CommandType type) { + super(type); + } + + /** + * Displays an exit message to the user. + * + * @param tasks a list of tasks the command may operate + * on + * @param manager a {@link TaskFileManager} instance that + * the command may use when saving changes + * made + * @param ui a {@link UserInterface} instance to display + * possible messages to the user + * @return a string representing the bot's response after execution of the command + */ + @Override + public String execute(TaskList tasks, TaskFileManager manager, UserInterface ui) { + return "That's all folks! Hope to see you again soon!"; + } + + /** + * Returns a boolean indicating whether the program should + * continue running after this command. Defaults to false. + * + * @return a boolean indicating whether the program should + * continue running; always false + */ + @Override + public boolean shouldContinue() { + return false; + } +} diff --git a/src/main/java/lawrence/command/FindTasksCommand.java b/src/main/java/lawrence/command/FindTasksCommand.java new file mode 100644 index 0000000000..88fd20e786 --- /dev/null +++ b/src/main/java/lawrence/command/FindTasksCommand.java @@ -0,0 +1,63 @@ +package lawrence.command; + +import lawrence.database.TaskFileManager; +import lawrence.task.TaskList; +import lawrence.ui.UserInterface; + +/** + * Represents the user command to search for tasks through the task description. + */ +public class FindTasksCommand extends Command { + private final String input; + + /** + * Default constructor. + * + * @param input the user input associated with this command + */ + public FindTasksCommand(CommandType type, String input) { + super(type); + this.input = input; + } + + /** + * Finds tasks that match the user query and displays them. Queries that + * partially match task descriptions are also returned. + *+ * Leading and trailing spaces are removed from the query to sanitise inputs. + * If the query is empty or only contains spaces, the user is notified and + * the method returns. + *
+ * + * @param tasks a list of tasks the command may operate + * on + * @param manager a {@link TaskFileManager} instance that + * the command may use when saving changes + * made + * @param ui a {@link UserInterface} instance to display + * possible messages to the user + * @return a string representing the bot's response after execution of the command + */ + @Override + public String execute(TaskList tasks, TaskFileManager manager, UserInterface ui) { + if (input.isEmpty()) { + return "Match query cannot be empty!"; + } + + String[] inputComponents = input.split(" ", 2); + if (inputComponents.length < 2) { + return "Please include a phrase for your query."; + } + + String query = inputComponents[1].trim(); + if (query.isEmpty()) { + return "Please include a phrase for your query."; + } + TaskList results = tasks.findTasks(query); + if (results.getSize() < 1) { + return "No matches were found for your query: " + query; + } + + return String.format("Here are the matching tasks in your list:%n%s", results); + } +} diff --git a/src/main/java/lawrence/command/UncompleteTaskCommand.java b/src/main/java/lawrence/command/UncompleteTaskCommand.java new file mode 100644 index 0000000000..f2b6999189 --- /dev/null +++ b/src/main/java/lawrence/command/UncompleteTaskCommand.java @@ -0,0 +1,65 @@ +package lawrence.command; + +import java.io.IOException; + +import lawrence.database.TaskFileManager; +import lawrence.task.Task; +import lawrence.task.TaskList; +import lawrence.ui.UserInterface; + +/** + * Represents the user command to mark existing tasks as incomplete. + */ +public class UncompleteTaskCommand extends Command { + private final String input; + + /** + * Default constructor. + * + * @param input the user input associated with this command + */ + public UncompleteTaskCommand(CommandType type, String input) { + super(type); + this.input = input; + } + + /** + * Finds the specified task and marks it as incomplete, then displays + * the new status of the task to the user. + *+ * If information about the task to mark incomplete is invalid, the + * method does nothing. + *
+ * + * @param tasks a list of tasks the command may operate + * on + * @param manager a {@link TaskFileManager} instance that + * the command may use when saving changes + * made + * @param ui a {@link UserInterface} instance to display + * possible messages to the user + * @return a string representing the bot's response after execution of the command + */ + @Override + public String execute(TaskList tasks, TaskFileManager manager, UserInterface ui) { + String[] inputComponents = input.split(" ", 2); + if (inputComponents.length < 2) { + return "Please specify the task you want to mark as incomplete."; + } + + String rawTaskNumber = inputComponents[1]; + try { + int taskNumber = Integer.parseInt(rawTaskNumber); + Task incompleteTask = tasks.uncompleteTask(taskNumber); + saveTasks(tasks, manager); + + return String.format("Changed your mind? The task is set to incomplete:%n%s", incompleteTask); + } catch (NumberFormatException e) { + return "Please specify a number to select a task."; + } catch (IllegalArgumentException | IllegalStateException e) { + return String.format("%s Please try again.", e.getMessage()); + } catch (IOException e) { + return String.format("Failed to mark task %s as incomplete. Please try again later.", rawTaskNumber); + } + } +} diff --git a/src/main/java/lawrence/database/TaskFileManager.java b/src/main/java/lawrence/database/TaskFileManager.java new file mode 100644 index 0000000000..7b2ffab57a --- /dev/null +++ b/src/main/java/lawrence/database/TaskFileManager.java @@ -0,0 +1,130 @@ +package lawrence.database; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Scanner; + +import lawrence.parser.InputSource; +import lawrence.parser.TaskParser; +import lawrence.task.Task; + +/** + * Interfaces with a text file to read and write tasks input by the user. + *+ * The contents in the file persist even after the program terminates. + *
+ */ +public class TaskFileManager { + private final File file; + + /** + * Default constructor. The {@link Path} instance provided can be a + * relative or absolute path. + * + * @param path the path to the text file used to store tasks + */ + public TaskFileManager(Path path) { + file = path.toAbsolutePath() + .normalize() + .toFile(); + } + + /** + * Reads from the file specified in the constructor, then converts the + * stored task strings into an array of {@link Task} objects using + * the {@link TaskParser} class. + *+ * If the text stored in the file is not in the correct format, the line + * is skipped and its contents are not returned in the array. + *
+ * + * @return an array of {@link Task} objects + * @throws IOException if reading from the file is unsuccessful + */ + public Task[] readTasksFromFile() throws IOException { + if (!file.exists()) { + return new Task[0]; + } + + Scanner sc = new Scanner(new FileReader(file)); + sc.useDelimiter("\n"); + + ArrayList+ * This operation will overwrite the contents previously in the text file. + *
+ * + * @param tasks the tasks to save to the text file + * @throws IOException if writing to the file is unsuccessful + */ + public void saveTasksToFile(Task[] tasks) throws IOException { + createFileIfNotExists(); + + FileWriter writer = new FileWriter(file); + String result = convertToSaveFormat(tasks); + writer.write(result); + writer.close(); + } + + /** + * Converts and array of {@link Task} objects into a string. + * + * @param tasks the array of {@link Task} objects + * @return a string representing the array of {@link Task} objects + */ + private String convertToSaveFormat(Task[] tasks) { + StringBuilder result = new StringBuilder(); + for (Task task : tasks) { + result.append(task.toSaveFormat()); + result.append("\n"); + } + return result.toString(); + } + + /** + * Creates a file to store tasks if no such file already exists. + *+ * The relevant parent directories are also created if needed. + *
+ * + * @throws IOException if the creation of the file was unsuccessful + */ + private void createFileIfNotExists() throws IOException { + if (file.exists()) { + return; + } + + boolean isParentDirectoryCreated = file.getParentFile().mkdirs(); + boolean isFileCreated = file.createNewFile(); + + if (!isParentDirectoryCreated) { + throw new IOException("An error occurred when trying to initialise the file directory " + file.getPath()); + } + + if (!isFileCreated) { + throw new IOException("An error occurred when trying to initialise " + file.getName()); + } + } +} diff --git a/src/main/java/lawrence/parser/CommandParser.java b/src/main/java/lawrence/parser/CommandParser.java new file mode 100644 index 0000000000..d3fe3b7c25 --- /dev/null +++ b/src/main/java/lawrence/parser/CommandParser.java @@ -0,0 +1,98 @@ +package lawrence.parser; + +import lawrence.command.AddTaskCommand; +import lawrence.command.Command; +import lawrence.command.CommandType; +import lawrence.command.CompleteTaskCommand; +import lawrence.command.DeleteTaskCommand; +import lawrence.command.DisplayTasksCommand; +import lawrence.command.ExitSessionCommand; +import lawrence.command.FindTasksCommand; +import lawrence.command.UncompleteTaskCommand; + +/** + * This class is used to make sense of text input by the user. + *+ * The commands are translated from text into their relevant command object + * counterparts as specified in {@link CommandType}. + *
+ */ +public class CommandParser { + /** + * Converts the provided input string into a relevant {@link Command} object. + *+ * The commands available are as specified in {@link CommandType} and are exhaustive. + * Any input that cannot be properly passed into a Command instance will result in + * an {@link IllegalArgumentException}. + *
+ * + * @param input the string containing a command to be parsed + * @return a {@link Command} object + * @throws IllegalArgumentException if the input cannot be parsed into a known command + * @throws IllegalStateException if the parsed {@link CommandType} is not recognised + */ + public static Command createCommand(String input) throws IllegalArgumentException, IllegalStateException { + if (input.isEmpty()) { + throw new IllegalArgumentException("Command input cannot be empty!"); + } + + String[] inputComponents = getInputComponents(input); + assert !inputComponents[0].isEmpty(); + CommandType type = determineCommandType(inputComponents[0]); + + Command c; + switch(type) { + case ADD_EVENT: + // fallthrough + case ADD_DEADLINE: + // fallthrough + case ADD_TODO: + c = new AddTaskCommand(type, input); + break; + case DELETE: + c = new DeleteTaskCommand(type, input); + break; + case DISPLAY: + c = new DisplayTasksCommand(type); + break; + case EXIT: + c = new ExitSessionCommand(type); + break; + case FIND_MATCHING: + c = new FindTasksCommand(type, input); + break; + case MARK_COMPLETE: + c = new CompleteTaskCommand(type, input); + break; + case MARK_INCOMPLETE: + c = new UncompleteTaskCommand(type, input); + break; + case INVALID: + throw new IllegalArgumentException(String.format("Unknown command: %s.", inputComponents[0])); + default: + throw new IllegalStateException(String.format("Unknown command type: %s", type)); + } + return c; + } + + /** + * Returns the components of a command from an input. + * + * @param input the full command input + * @return an array of length 2 containing the split input + */ + private static String[] getInputComponents(String input) { + return input.split(" ", 2); + } + + /** + * Returns a {@link CommandType} corresponding to the given input. + * + * @param input the input string containing information about a {@link CommandType} + * @return the {@link CommandType} corresponding to the input + * @throws IllegalArgumentException if input string does not match any command type + */ + private static CommandType determineCommandType(String input) throws IllegalArgumentException { + return CommandType.fromString(input); + } +} diff --git a/src/main/java/lawrence/parser/FileTaskCreator.java b/src/main/java/lawrence/parser/FileTaskCreator.java new file mode 100644 index 0000000000..4fd23a4b88 --- /dev/null +++ b/src/main/java/lawrence/parser/FileTaskCreator.java @@ -0,0 +1,141 @@ +package lawrence.parser; + +import java.time.LocalDateTime; + +import lawrence.task.Deadline; +import lawrence.task.Event; +import lawrence.task.Task; +import lawrence.task.TaskType; +import lawrence.task.Todo; +import lawrence.utils.DateParser; + +/** + * The concrete implementation of {@link TaskCreator} used to parse + * file input into a {@link Task} object. + */ +public class FileTaskCreator implements TaskCreator { + private static final int NUMBER_OF_DEADLINE_PARAMETERS = 3; + private static final int NUMBER_OF_EVENT_PARAMETERS = 4; + private static final int NUMBER_OF_TODO_PARAMETERS = 2; + + /** + * Converts a string input from a file containing task information + * into a {@link Task} object. + * + * @param input the string containing information about a task object + * @return a {@link Task} object + * @throws IllegalArgumentException if input is invalid + */ + @Override + public Task createTask(String input) { + if (input.isEmpty()) { + throw new IllegalArgumentException("Cannot create task from empty input!"); + } + + // separate the string containing information about the task type into index 0 + String[] inputComponents = input.split(" \\| ", 2); + + if (inputComponents.length != 2) { + throw new IllegalArgumentException("Unable to parse input from file"); + } + + // parse the type of task that needs to be created + TaskType type = TaskType.fromString(inputComponents[0]); + + Task t; + switch (type) { + case DEADLINE: + t = createDeadlineTask(inputComponents[1]); + break; + case EVENT: + t = createEventTask(inputComponents[1]); + break; + case TODO: + t = createTodoTask(inputComponents[1]); + break; + default: + throw new IllegalArgumentException("Unknown task type: " + type); + } + return t; + } + + /** + * Creates and returns a {@link Deadline} object based on the + * input information provided. + * + * @param input a string containing information about the {@link Deadline} object + * @return a {@link Deadline} object + * @throws IllegalArgumentException if the input is invalid + * @see Deadline + */ + private Deadline createDeadlineTask(String input) throws IllegalArgumentException { + String[] parameters = input.split(" \\| ", NUMBER_OF_DEADLINE_PARAMETERS); + + if (parameters.length != NUMBER_OF_DEADLINE_PARAMETERS) { + throw new IllegalArgumentException( + String.format("Unable to parse deadline from file input. Expected %d parameters, got %d", + NUMBER_OF_DEADLINE_PARAMETERS, + parameters.length)); + } + + // deconstruct array elements into their respective attributes + boolean isComplete = parameters[0].equals("1"); + String description = parameters[1]; + LocalDateTime by = DateParser.parseFileInputDate(parameters[2]); + + return new Deadline(description, isComplete, by); + } + + /** + * Creates and returns an {@link Event} object based on the + * input information provided. + * + * @param input a string containing information about the {@link Event} object + * @return an {@link Event} object + * @throws IllegalArgumentException if the input is invalid + * @see Event + */ + private Event createEventTask(String input) throws IllegalArgumentException { + String[] parameters = input.split(" \\| ", NUMBER_OF_EVENT_PARAMETERS); + + if (parameters.length != NUMBER_OF_EVENT_PARAMETERS) { + throw new IllegalArgumentException( + String.format("Unable to parse event from file input. Expected %d parameters, got %d", + NUMBER_OF_EVENT_PARAMETERS, + parameters.length)); + } + + // deconstruct array elements into their respective attributes + boolean isComplete = parameters[0].equals("1"); + String description = parameters[1]; + LocalDateTime from = DateParser.parseFileInputDate(parameters[2]); + LocalDateTime to = DateParser.parseFileInputDate(parameters[3]); + + return new Event(description, isComplete, from, to); + } + + /** + * Creates and returns a {@link Todo} object based on the + * input information provided. + * + * @param input a string containing information about the {@link Todo} object + * @return a {@link Todo} object + * @see Todo + */ + private Todo createTodoTask(String input) { + String[] parameters = input.split(" \\| ", NUMBER_OF_TODO_PARAMETERS); + + if (parameters.length != NUMBER_OF_TODO_PARAMETERS) { + throw new IllegalArgumentException( + String.format("Unable to parse todo from file input. Expected %d parameters, got %d", + NUMBER_OF_TODO_PARAMETERS, + parameters.length)); + } + + // deconstruct array elements into their respective attributes + boolean isComplete = parameters[0].equals("1"); + String description = parameters[1]; + + return new Todo(description, isComplete); + } +} diff --git a/src/main/java/lawrence/parser/InputSource.java b/src/main/java/lawrence/parser/InputSource.java new file mode 100644 index 0000000000..ce21393d11 --- /dev/null +++ b/src/main/java/lawrence/parser/InputSource.java @@ -0,0 +1,9 @@ +package lawrence.parser; + +/** + * Represents the different sources that a string input can come from. + */ +public enum InputSource { + FILE, + USER +} diff --git a/src/main/java/lawrence/parser/TaskCreator.java b/src/main/java/lawrence/parser/TaskCreator.java new file mode 100644 index 0000000000..3722990be8 --- /dev/null +++ b/src/main/java/lawrence/parser/TaskCreator.java @@ -0,0 +1,17 @@ +package lawrence.parser; + +import lawrence.task.Task; + +/** + * Represents an object that can receive a string as input + * and convert it into a relevant {@link Task} object. + */ +public interface TaskCreator { + /** + * Converts the given input into a {@link Task} object. + * + * @param input the string containing information about a task object + * @return a {@link Task} object + */ + Task createTask(String input); +} diff --git a/src/main/java/lawrence/parser/TaskParser.java b/src/main/java/lawrence/parser/TaskParser.java new file mode 100644 index 0000000000..c6dff88492 --- /dev/null +++ b/src/main/java/lawrence/parser/TaskParser.java @@ -0,0 +1,47 @@ +package lawrence.parser; + +import lawrence.task.Task; + +/** + * This class is used to make sense of text read from a file. + *+ * The commands are translated from text into their relevant command object + * counterparts as specified in {@link lawrence.task.TaskType}. + *
+ */ +public class TaskParser { + /** + * Converts the provided input string into a relevant {@link Task} object + * based on the {@link InputSource} specified. + *+ * The input sources available are as specified in {@link InputSource} and are exhaustive. + * If the input source is unrecognised, an {@link IllegalStateException} is thrown. + *
+ * + * @param input the string containing task information to be parsed + * @return a {@link Task} object + * @throws IllegalArgumentException if the input is invalid + * @throws IllegalStateException if the {@link InputSource} is not recognised + */ + public static Task createTask(String input, InputSource source) + throws IllegalArgumentException, IllegalStateException { + + if (input.isEmpty()) { + throw new IllegalArgumentException("Task input cannot be empty!"); + } + + TaskCreator creator; + switch(source) { + case FILE: + creator = new FileTaskCreator(); + break; + case USER: + creator = new UserTaskCreator(); + break; + default: + // this case should never be reached + throw new IllegalStateException("Unexpected source: " + source); + } + return creator.createTask(input); + } +} diff --git a/src/main/java/lawrence/parser/UserTaskCreator.java b/src/main/java/lawrence/parser/UserTaskCreator.java new file mode 100644 index 0000000000..39defcf0d0 --- /dev/null +++ b/src/main/java/lawrence/parser/UserTaskCreator.java @@ -0,0 +1,126 @@ +package lawrence.parser; + +import java.time.LocalDateTime; + +import lawrence.task.Deadline; +import lawrence.task.Event; +import lawrence.task.Task; +import lawrence.task.TaskType; +import lawrence.task.Todo; +import lawrence.utils.DateParser; + +/** + * The concrete implementation of {@link TaskCreator} used to parse + * user input into a {@link Task} object. + */ +public class UserTaskCreator implements TaskCreator { + private static final int NUMBER_OF_DEADLINE_PARAMETERS = 2; + private static final int NUMBER_OF_EVENT_PARAMETERS = 3; + + /** + * Converts a string input by the user containing task information + * into a {@link Task} object. + * + * @param input the string containing information about a task object + * @return a {@link Task} object + * @throws IllegalArgumentException if input is invalid + */ + @Override + public Task createTask(String input) throws IllegalArgumentException { + if (input.isEmpty()) { + throw new IllegalArgumentException("Cannot create task from empty input!"); + } + + // separates the string containing information about the task type into index 0 + String[] inputComponents = input.split(" ", 2); + + if (inputComponents.length != 2) { + throw new IllegalArgumentException("Task information cannot be empty."); + } + + // parse the type of task that needs to be created + TaskType type = TaskType.fromString(inputComponents[0]); + + Task t; + switch (type) { + case DEADLINE: + t = createDeadlineTask(inputComponents[1]); + break; + case EVENT: + t = createEventTask(inputComponents[1]); + break; + case TODO: + t = createTodoTask(inputComponents[1]); + break; + default: + throw new IllegalArgumentException("Unknown task type: " + type); + } + return t; + } + + /** + * Creates and returns a {@link Deadline} object based on the + * input information provided. + * + * @param input a string containing information about the {@link Deadline} object + * @return a {@link Deadline} object + * @throws IllegalArgumentException if the input is invalid + * @see Deadline + */ + private Deadline createDeadlineTask(String input) throws IllegalArgumentException { + String[] parameters = input.split(" /by ", NUMBER_OF_DEADLINE_PARAMETERS); + + if (parameters.length != NUMBER_OF_DEADLINE_PARAMETERS) { + throw new IllegalArgumentException( + String.format("Unable to create deadline from user input. Expected %d parameters, got %d", + NUMBER_OF_DEADLINE_PARAMETERS, + parameters.length)); + } + + // deconstruct array elements into their respective attributes + String description = parameters[0]; + LocalDateTime by = DateParser.parseUserInputDate(parameters[1]); + + return new Deadline(description, by); + } + + /** + * Creates and returns an {@link Event} object based on the + * input information provided. + * + * @param input a string containing information about the {@link Event} object + * @return an {@link Event} object + * @throws IllegalArgumentException if the input is invalid + * @see Event + */ + private Event createEventTask(String input) { + String[] parameters = input.split(" /from | /to ", NUMBER_OF_EVENT_PARAMETERS); + if (parameters.length != NUMBER_OF_EVENT_PARAMETERS) { + throw new IllegalArgumentException( + String.format("Unable to create event from user input. Expected %d parameters, got %d", + NUMBER_OF_EVENT_PARAMETERS, + parameters.length)); + } + + assert parameters.length == NUMBER_OF_EVENT_PARAMETERS; + + // deconstruct array elements into their respective attributes + String description = parameters[0]; + LocalDateTime from = DateParser.parseUserInputDate(parameters[1]); + LocalDateTime to = DateParser.parseUserInputDate(parameters[2]); + + return new Event(description, from, to); + } + + /** + * Creates and returns a {@link Todo} object based on the + * input information provided. + * + * @param input a string containing information about the {@link Todo} object + * @return a {@link Todo} object + * @see Todo + */ + private Todo createTodoTask(String input) { + return new Todo(input); + } +} diff --git a/src/main/java/lawrence/task/Deadline.java b/src/main/java/lawrence/task/Deadline.java new file mode 100644 index 0000000000..709ff385a2 --- /dev/null +++ b/src/main/java/lawrence/task/Deadline.java @@ -0,0 +1,67 @@ +package lawrence.task; + +import java.time.LocalDateTime; + +import lawrence.utils.DateParser; + +/** + * Represents a task with a deadline. + */ +public class Deadline extends Task { + private final LocalDateTime by; + + /** + * Constructor. Creates a {@link Deadline} object with the specified + * task description and completion date. + *+ * The task will be marked as incomplete by default. + *
+ * + * @param description the name of the deadline + * @param by the date to complete the deadline by + */ + public Deadline(String description, LocalDateTime by) { + super(description); + this.by = by; + } + + /** + * Constructor. Creates a {@link Deadline} object with the specified + * task description, completion status and completion date. + *+ * The task can be marked as complete or incomplete on creation. + *
+ * + * @param description the name of the deadline + * @param isComplete indicates whether the deadline is complete + * @param by the date by which the deadline should be completed + */ + public Deadline(String description, boolean isComplete, LocalDateTime by) { + super(description, isComplete); + this.by = by; + } + + /** + * Returns a string representation of the object in a + * standardised save format. + * + * @return a string representation of the object in save format + */ + public String toSaveFormat() { + return String.format("D | %s | %s", + super.toSaveFormat(), + DateParser.toOutputString(by)); + } + + /** + * Returns a string representation of the object. + * + * @return a string representation of the object + */ + @Override + public String toString() { + return String.format("[D]%s (by: %s)", + super.toString(), + DateParser.toOutputString(by)); + } +} diff --git a/src/main/java/lawrence/task/Event.java b/src/main/java/lawrence/task/Event.java new file mode 100644 index 0000000000..123e7ef367 --- /dev/null +++ b/src/main/java/lawrence/task/Event.java @@ -0,0 +1,73 @@ +package lawrence.task; + +import java.time.LocalDateTime; + +import lawrence.utils.DateParser; + +/** + * Represents a task that has a start time and an end time. + */ +public class Event extends Task { + private final LocalDateTime from; + private final LocalDateTime to; + + /** + * Constructor. Creates an {@link Event} object with the specified + * task description, start time and end time. + *+ * The task will be marked as incomplete by default. + *
+ * + * @param description the name of the event + * @param from the start date of the event + * @param to the end date of the event + */ + public Event(String description, LocalDateTime from, LocalDateTime to) { + super(description); + this.from = from; + this.to = to; + } + + /** + * Constructor. Creates an {@link Event} object with the specified + * task description, completion status, start time and end time. + *+ * The task can be marked as complete or incomplete on creation. + *
+ * + * @param description the name of the event + * @param isComplete indicates whether the event is complete + * @param from the start date of the event + * @param to the end date of the event + */ + public Event(String description, boolean isComplete, LocalDateTime from, LocalDateTime to) { + super(description, isComplete); + this.from = from; + this.to = to; + } + + /** + * Returns a string representation of the object in a + * standardised save format. + * + * @return a string representation of the object in save format + */ + public String toSaveFormat() { + return String.format("E | %s | %s | %s", + super.toSaveFormat(), + DateParser.toOutputString(from), + DateParser.toOutputString(to)); + } + + /** + * Returns a string representation of the object. + * + * @return a string representation of the object + */ + @Override + public String toString() { + return String.format("[E]%s (from: %s to: %s)", super.toString(), + DateParser.toOutputString(from), + DateParser.toOutputString(to)); + } +} diff --git a/src/main/java/lawrence/task/Task.java b/src/main/java/lawrence/task/Task.java new file mode 100644 index 0000000000..0140268d5c --- /dev/null +++ b/src/main/java/lawrence/task/Task.java @@ -0,0 +1,77 @@ +package lawrence.task; + +/** + * Represents a real-life task that needs to be completed. + */ +public abstract class Task { + private boolean isComplete; + private final String description; + + /** + * Constructor. Creates a {@link Task} object with the specified + * task description. + *+ * The task will be marked as incomplete by default. + *
+ * @param description the name of the task + */ + public Task(String description) { + this.description = description; + isComplete = false; + } + + /** + * Constructor. Creates a {@link Task} object with the specified + * task description and completion status. + *+ * The task can be marked as complete or incomplete on creation. + *
+ * + * @param description the name of the task + * @param isComplete indicates whether the task is complete + */ + public Task(String description, boolean isComplete) { + this.description = description; + this.isComplete = isComplete; + } + + /** + * Returns a boolean indicate if the query matches or partially matches + * the task description. + * + * @param query the string to match with the description + * @return true if there is a match or partial match, false otherwise + */ + public boolean contains(String query) { + return description.contains(query); + } + + /** + * Sets the completion status of the task to the specified value. + * + * @param isComplete indicates whether the task is complete + */ + public void setComplete(boolean isComplete) { + this.isComplete = isComplete; + } + + /** + * Returns a string representation of the object in a + * standardised save format. + * + * @return a string representation of the object in save format + */ + public String toSaveFormat() { + return String.format("%s | %s", isComplete ? "1" : "0", description); + } + + /** + * Returns a string representation of the object. + * + * @return a string representation of the object + */ + @Override + public String toString() { + return String.format("[%s] %s", isComplete ? "X" : " ", description); + } +} diff --git a/src/main/java/lawrence/task/TaskList.java b/src/main/java/lawrence/task/TaskList.java new file mode 100644 index 0000000000..f3e6292e9c --- /dev/null +++ b/src/main/java/lawrence/task/TaskList.java @@ -0,0 +1,169 @@ +package lawrence.task; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents the list of tasks the user has specified to be tracked by the chatbot. + */ +public class TaskList { + private final ArrayList+ * Task numbers start from one. + *
+ * + * @param taskNumber the index of the task to delete. Starts from 1 + * @return the {@link Task} object removed from the list + * @throws IllegalArgumentException if the task number provided is out of bounds + * @throws IllegalStateException if there are no tasks in the list + */ + public Task deleteTask(int taskNumber) throws IllegalArgumentException, IllegalStateException { + if (tasks.isEmpty()) { + throw new IllegalStateException("There are no tasks that can be chosen for deletion."); + } + + if (taskNumber < 1 || taskNumber > tasks.size()) { + throw new IllegalArgumentException( + String.format("Task does not exist. Number must be within the range 1 to %s.", tasks.size())); + } + + return tasks.remove(taskNumber - 1); + } + + /** + * Marks a task from the list as complete based on the number provided. + *+ * Task numbers start from one. + *
+ * + * @param taskNumber the index of the task to delete. Starts from 1 + * @return the {@link Task} object that was updated in the operation + * @throws IllegalArgumentException if the task number provided is out of bounds + * @throws IllegalStateException if there are no tasks in the list + */ + public Task completeTask(int taskNumber) { + if (tasks.isEmpty()) { + throw new IllegalStateException("There are no tasks that can be chosen to be marked as complete."); + } + + if (taskNumber < 1 || taskNumber > tasks.size()) { + throw new IllegalArgumentException( + String.format("Task does not exist. Number must be within the range 1 to %s.", tasks.size())); + } + + Task t = tasks.get(taskNumber - 1); + assert t != null; + t.setComplete(true); + return t; + } + + /** + * Marks a task from the list as incomplete based on the number provided. + *+ * Task numbers start from one. + *
+ * + * @param taskNumber the index of the task to delete. Starts from 1 + * @return the {@link Task} object that was updated in the operation + * @throws IllegalArgumentException if the task number provided is out of bounds + * @throws IllegalStateException if there are no tasks in the list + */ + public Task uncompleteTask(int taskNumber) { + if (tasks.isEmpty()) { + throw new IllegalStateException("There are no tasks that can be chosen to be marked as incomplete."); + } + + if (taskNumber < 1 || taskNumber > tasks.size()) { + throw new IllegalArgumentException( + String.format("Task does not exist. Number must be within the range 1 to %s.", tasks.size())); + } + + Task t = tasks.get(taskNumber - 1); + t.setComplete(false); + return t; + } + + /** + * Matches the specified query with tasks descriptions and returns a + * {@link TaskList} containing tasks which match the query. + * + * @param query the string to match task descriptions + * @return a {@link TaskList} containing hits + */ + public TaskList findTasks(String query) { + List+ * The input string is converted into lowercase for greater input flexibility. + *
+ * + * @param type the string containing an enum value + */ + TaskType(String type) { + this.taskType = type.toLowerCase(); + } + + /** + * Converts a text string into its enum counterpart. + * + * @param input the input containing an enum value + * @return a TaskType enum if it exists + * @throws IllegalArgumentException if string does not match any enum values + */ + public static TaskType fromString(String input) throws IllegalArgumentException { + for (TaskType task : TaskType.values()) { + String taskType = task.getTaskType(); + // Check for exact match + if (taskType.equalsIgnoreCase(input)) { + return task; + } + + // Check for match by first letter if no exact match found + if (taskType.startsWith(String.valueOf(input.charAt(0)).toLowerCase())) { + return task; + } + } + throw new IllegalArgumentException("No task type found for input: " + input); + } + + /** + * Returns the task type as a string. + * + * @return the task type as a string + */ + public String getTaskType() { + return taskType; + } +} diff --git a/src/main/java/lawrence/task/Todo.java b/src/main/java/lawrence/task/Todo.java new file mode 100644 index 0000000000..251779b633 --- /dev/null +++ b/src/main/java/lawrence/task/Todo.java @@ -0,0 +1,53 @@ +package lawrence.task; + +/** + * Represents a simple task with no time constraints. + */ +public class Todo extends Task { + /** + * Constructor. Creates an {@link Todo} object with the specified + * task description. + *+ * The task will be marked as incomplete by default. + *
+ * + * @param description the name of the task + */ + public Todo(String description) { + super(description); + } + + /** + * Constructor. Creates an {@link Todo} object with the specified + * task description and completion status. + *+ * The task can be marked as complete or incomplete on creation. + *
+ * + * @param description the name of the task + * @param isComplete indicates whether the task is complete + */ + public Todo(String description, boolean isComplete) { + super(description, isComplete); + } + + /** + * Returns a string representation of the object in a + * standardised save format. + * + * @return a string representation of the object in save format + */ + public String toSaveFormat() { + return String.format("T | %s", super.toSaveFormat()); + } + + /** + * Returns a string representation of the object. + * + * @return a string representation of the object + */ + @Override + public String toString() { + return String.format("[T]%s", super.toString()); + } +} diff --git a/src/main/java/lawrence/ui/Launcher.java b/src/main/java/lawrence/ui/Launcher.java new file mode 100644 index 0000000000..9812798655 --- /dev/null +++ b/src/main/java/lawrence/ui/Launcher.java @@ -0,0 +1,12 @@ +package lawrence.ui; + +import javafx.application.Application; + +/** + * A launcher class to work around classpath issues. + */ +public class Launcher { + public static void main(String[] args) { + Application.launch(Main.class, args); + } +} diff --git a/src/main/java/lawrence/ui/Main.java b/src/main/java/lawrence/ui/Main.java new file mode 100644 index 0000000000..0cf6c7ebf6 --- /dev/null +++ b/src/main/java/lawrence/ui/Main.java @@ -0,0 +1,36 @@ +package lawrence.ui; + +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; +import lawrence.app.Lawrence; +import lawrence.ui.components.MainWindow; + +/** + * A GUI for Lawrence using FXML. + */ +public class Main extends Application { + private final Lawrence lawrence = new Lawrence(); + + @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); + stage.setTitle("Lawrence Chatbot"); + + fxmlLoader.+ * Clears the user input field after processing. + *
+ */ + @FXML + private void handleUserInput() { + String input = userInput.getText(); + Response response = lawrence.getResponse(input); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(input, userImage), + DialogBox.getBotDialog(response, botImage) + ); + + if (!response.shouldContinue()) { + Platform.exit(); + } + + userInput.clear(); + scrollToBottom(); + } + + /** + * Displays a welcome message. + */ + public void showWelcomeMessage() { + Response welcomeResponse = new Response(CommandType.INVALID, + lawrence.getWelcomeMessage(), + true); + dialogContainer.getChildren().add( + DialogBox.getBotDialog(welcomeResponse, botImage)); + } + + /** + * Initialises a listener to handle scroll events. + */ + private void setScrollListener() { + dialogContainer.setOnScroll(event -> { + // get change in scroll direction + double deltaY = event.getDeltaY(); + double scrollAmount = scrollPane.getVvalue() - deltaY + / scrollPane.getContent().getBoundsInLocal().getHeight(); + + scrollPane.setVvalue(scrollAmount); + }); + } + + /** + * Sets the vertical value of the scroll pane to emulate scrolling to the bottom of the dialog box. + */ + private void scrollToBottom() { + // ensure layout is updated before scrolling + dialogContainer.layout(); + scrollPane.layout(); + + // scroll to the bottom + scrollPane.setVvalue(1.0); + } +} diff --git a/src/main/java/lawrence/utils/DateParser.java b/src/main/java/lawrence/utils/DateParser.java new file mode 100644 index 0000000000..433dc51c6b --- /dev/null +++ b/src/main/java/lawrence/utils/DateParser.java @@ -0,0 +1,49 @@ +package lawrence.utils; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * A utility class used to standardise how dates are stored and displayed. + */ +public class DateParser { + public static final String FORMAT_STRING_FOR_USER_INPUT = "yyyy-MM-dd HH:mm"; + public static final String FORMAT_STRING_FOR_STORAGE = "dd-MM-yyyy HH:mm"; + private static final DateTimeFormatter FORMATTER_FOR_USER_INPUT = DateTimeFormatter + .ofPattern(FORMAT_STRING_FOR_USER_INPUT); + private static final DateTimeFormatter FORMATTER_FOR_STORAGE = DateTimeFormatter + .ofPattern(FORMAT_STRING_FOR_STORAGE); + + /** + * Converts user input into a {@link LocalDateTime} object. + * Formatting is based on {@link #FORMAT_STRING_FOR_USER_INPUT}. + * + * @param input the string to convert to a {@link LocalDateTime} object + * @return a {@link LocalDateTime} object + */ + public static LocalDateTime parseUserInputDate(String input) { + return LocalDateTime.parse(input, FORMATTER_FOR_USER_INPUT); + } + + /** + * Converts file input into a {@link LocalDateTime} object. + * Formatting is based on {@link #FORMAT_STRING_FOR_STORAGE}. + * + * @param input the string to convert to a {@link LocalDateTime} object + * @return a {@link LocalDateTime} object + */ + public static LocalDateTime parseFileInputDate(String input) { + return LocalDateTime.parse(input, FORMATTER_FOR_STORAGE); + } + + /** + * Converts a {@link LocalDateTime} object into a string. + * Formatting is based on {@link #FORMAT_STRING_FOR_STORAGE}. + * + * @param input the {@link LocalDateTime} object to be converted to a string + * @return a string representing the {@link LocalDateTime} object + */ + public static String toOutputString(LocalDateTime input) { + return input.format(FORMATTER_FOR_STORAGE); + } +} diff --git a/src/main/resources/css/dialog-box.css b/src/main/resources/css/dialog-box.css new file mode 100644 index 0000000000..819c665b1f --- /dev/null +++ b/src/main/resources/css/dialog-box.css @@ -0,0 +1,38 @@ +.label { + -fx-background-color: #1fc600; + -fx-border-color: #089000; + -fx-border-width: 2px; + -fx-background-radius: 1em 1em 0 1em; + -fx-border-radius: 1em 1em 0 1em; +} + +.reply-label { + -fx-background-color: #CCCCCC; + -fx-border-color: #B3B3B3; + -fx-background-radius: 1em 1em 1em 0; + -fx-border-radius: 1em 1em 1em 0; +} + +.add-label { + -fx-background-color: yellow; +} + +.marked-label { + -fx-background-color: palegreen; +} + +.delete-label { + -fx-background-color: lightpink; +} + +#displayPicture { + /* Shadow effect on image. */ + -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.2), 10, 0.5, 5, 5); + + /* Change size of image. */ + -fx-scale-x: 1; + -fx-scale-y: 1; + + /* Rotate image clockwise by degrees. */ + -fx-rotate: 0; +} \ No newline at end of file diff --git a/src/main/resources/css/main.css b/src/main/resources/css/main.css new file mode 100644 index 0000000000..07c1e63758 --- /dev/null +++ b/src/main/resources/css/main.css @@ -0,0 +1,51 @@ +.root { + main-color: rgb(237, 255, 242); /* Create a looked-up color called "main-color" within root. */ + -fx-background-color: main-color; + -fx-background-image: url("../images/background.jpg"); /* Set image */ + -fx-background-size: cover; +} + +.text-field { + -fx-background-color: #d9ffe2; + -fx-font: 20px "Arial"; +} + +.button { + -fx-background-color: mediumspringgreen; + -fx-font: italic bold 16px "Arial"; +} + +.button:hover { + -fx-background-color:cyan; + -fx-font-size: 18px; +} + +.button:pressed { + -fx-background-color:orange; + -fx-font-size: 20px; +} + +.scroll-pane { + -fx-background-color: rgba(0,0,0,.5); +} + +.scroll-pane .viewport { + -fx-background-color: transparent; +} + +.scroll-bar { + -fx-font-size: 10px; /* Change width of scroll bar. */ + -fx-background-color: main-color; +} + +.scroll-bar .thumb { + -fx-background-color: #808080; + -fx-background-radius: 1em; +} + +/* Hides the increment and decrement buttons. */ +.scroll-bar .increment-button, +.scroll-bar .decrement-button { + -fx-pref-height: 0; + -fx-opacity: 0; +} \ No newline at end of file diff --git a/src/main/resources/images/background.jpg b/src/main/resources/images/background.jpg new file mode 100644 index 0000000000..acfbe93e5a Binary files /dev/null and b/src/main/resources/images/background.jpg differ diff --git a/src/main/resources/images/baller.jpg b/src/main/resources/images/baller.jpg new file mode 100644 index 0000000000..50ae877878 Binary files /dev/null and b/src/main/resources/images/baller.jpg differ diff --git a/src/main/resources/images/lawrence.jpg b/src/main/resources/images/lawrence.jpg new file mode 100644 index 0000000000..df099a5054 Binary files /dev/null and b/src/main/resources/images/lawrence.jpg differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..821bd6e218 --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,22 @@ + + + + + + + +