From 2337daa00c3b9b8e21fcbb1358b2b21d5f87b8b3 Mon Sep 17 00:00:00 2001 From: ryanguai Date: Sat, 8 Oct 2022 22:35:43 +0800 Subject: [PATCH 01/31] Add support for adding of tags to tasks --- .../address/logic/commands/AddTagCommand.java | 160 ++++++++++++++++-- .../logic/parser/AddTagCommandParser.java | 28 +-- .../logic/parser/AddTaskCommandParser.java | 6 +- .../seedu/address/logic/parser/CliSyntax.java | 4 +- .../java/seedu/address/model/AddressBook.java | 16 ++ src/main/java/seedu/address/model/Model.java | 4 + .../seedu/address/model/ModelManager.java | 13 ++ .../java/seedu/address/model/task/Task.java | 36 +++- .../seedu/address/model/task/TaskList.java | 29 ++++ 9 files changed, 260 insertions(+), 36 deletions(-) diff --git a/src/main/java/seedu/address/logic/commands/AddTagCommand.java b/src/main/java/seedu/address/logic/commands/AddTagCommand.java index 4a90243913a..88f0e5ee1b1 100644 --- a/src/main/java/seedu/address/logic/commands/AddTagCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddTagCommand.java @@ -3,6 +3,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; import java.util.Collections; import java.util.HashSet; @@ -21,6 +22,8 @@ import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Description; +import seedu.address.model.task.Task; /** * Edits the details of an existing person in the address book. @@ -40,42 +43,72 @@ public class AddTagCommand extends Command { public static final String MESSAGE_ADD_TAG_SUCCESS = "Added tag: %1$s"; public static final String MESSAGE_TAG_NOT_ADDED = "At least 1 tag to add must be provided."; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + public static final String MESSAGE_MISSING_INDEX = "At least 1 contact or task index must be provided."; private final Index index; private final EditPersonDescriptor editPersonDescriptor; + private final EditTaskDescriptor editTaskDescriptor; + private final boolean addTagToContact; + private final boolean addTagToTask; /** * @param index of the person in the filtered person list to edit * @param editPersonDescriptor details to edit the person with */ - public AddTagCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + public AddTagCommand(Index index, EditPersonDescriptor editPersonDescriptor, boolean addTagToContact, boolean addTagToTask, EditTaskDescriptor editTaskDescriptor) { requireNonNull(index); requireNonNull(editPersonDescriptor); this.index = index; this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); + this.addTagToContact = addTagToContact; + this.addTagToTask = addTagToTask; + this.editTaskDescriptor = new EditTaskDescriptor(editTaskDescriptor); } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } + if (addTagToContact) { + List lastShownPersonList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownPersonList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + Person personToEdit = lastShownPersonList.get(index.getZeroBased()); + Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.setPerson(personToEdit, editedPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_ADD_TAG_SUCCESS, + editPersonDescriptor.getTags().orElse(new HashSet<>()))); } + if (addTagToTask) { + List lastShownTaskList = model.getFilteredTaskList(); - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_ADD_TAG_SUCCESS, - editPersonDescriptor.getTags().orElse(new HashSet<>()))); + if (index.getZeroBased() >= lastShownTaskList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Task taskToEdit = lastShownTaskList.get(index.getZeroBased()); + Task editedTask = createEditedTask(taskToEdit, editTaskDescriptor); + + if (!taskToEdit.isSameTask(editedTask) && model.hasTask(editedTask)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.setTask(taskToEdit, editedTask); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + return new CommandResult(String.format(MESSAGE_ADD_TAG_SUCCESS, + editTaskDescriptor.getTags().orElse(new HashSet<>()))); + } + throw new CommandException(MESSAGE_MISSING_INDEX); } /** @@ -99,6 +132,25 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); } + /** + * Creates and returns a {@code Person} with the details of {@code personToEdit} + * edited with {@code editPersonDescriptor}. + */ + private static Task createEditedTask(Task taskToEdit, EditTaskDescriptor editTaskDescriptor) { + assert taskToEdit != null; + + Description updatedDescription = editTaskDescriptor.getDescription().orElse(taskToEdit.getDescription()); + boolean updatedIsDone = editTaskDescriptor.getIsDone().orElse(taskToEdit.getIsDone()); + Set newTags = editTaskDescriptor.getTags().orElse(new HashSet<>()); + Set updatedTags = new HashSet<>(); + updatedTags.addAll(taskToEdit.getTags()); + if (newTags.size() > 0) { + updatedTags.addAll(newTags); + } + + return new Task(updatedDescription, updatedIsDone, updatedTags); + } + @Override public boolean equals(Object other) { // short circuit if same object @@ -220,5 +272,87 @@ && getAddress().equals(e.getAddress()) && getTags().equals(e.getTags()); } } + + /** + * Stores the details to edit the task with. Each non-empty field value will replace the + * corresponding field value of the task. + */ + public static class EditTaskDescriptor { + private Description description; + private boolean isDone; + private Set tags; + + public EditTaskDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditTaskDescriptor(EditTaskDescriptor toCopy) { + setDescription(toCopy.description); + setIsDone(toCopy.isDone); + setTags(toCopy.tags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(description, isDone, tags); + } + + public void setDescription(Description description) { + this.description = description; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + public void setIsDone(boolean isDone) { + this.isDone = isDone; + } + + public Optional getIsDone() { + return Optional.ofNullable(isDone); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditPersonDescriptor)) { + return false; + } + + // state check + EditPersonDescriptor e = (EditPersonDescriptor) other; + + return getDescription().equals(e.getName()) + && getIsDone().equals(e.getPhone()) + && getTags().equals(e.getTags()); + } + } } diff --git a/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java b/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java index 7e563f15723..7de9f771f62 100644 --- a/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddTagCommandParser.java @@ -2,11 +2,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.*; import java.util.Collection; import java.util.Collections; @@ -32,10 +28,13 @@ public class AddTagCommandParser implements Parser { public AddTagCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_CONTACT, PREFIX_TASK, PREFIX_TAG); Index index; + boolean addTagToContact = argMultimap.getValue(PREFIX_CONTACT).isPresent(); + boolean addTagToTask = argMultimap.getValue(PREFIX_TASK).isPresent(); + try { index = ParserUtil.parseIndex(argMultimap.getPreamble()); } catch (ParseException pe) { @@ -43,25 +42,16 @@ public AddTagCommand parse(String args) throws ParseException { } EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); - } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); - } parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + AddTagCommand.EditTaskDescriptor editTaskDescriptor = new AddTagCommand.EditTaskDescriptor(); + parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editTaskDescriptor::setTags); + if (!editPersonDescriptor.isAnyFieldEdited()) { throw new ParseException(AddTagCommand.MESSAGE_TAG_NOT_ADDED); } - return new AddTagCommand(index, editPersonDescriptor); + return new AddTagCommand(index, editPersonDescriptor, addTagToContact, addTagToTask, editTaskDescriptor); } /** diff --git a/src/main/java/seedu/address/logic/parser/AddTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/AddTaskCommandParser.java index 69823640c58..b6d1af9fbd5 100644 --- a/src/main/java/seedu/address/logic/parser/AddTaskCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddTaskCommandParser.java @@ -4,10 +4,13 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DEADLINE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DESCRIPTION; +import java.util.HashSet; +import java.util.Set; import java.util.stream.Stream; import seedu.address.logic.commands.AddTaskCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; import seedu.address.model.task.Description; import seedu.address.model.task.Task; @@ -31,8 +34,9 @@ public AddTaskCommand parse(String args) throws ParseException { } Description description = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_TASK_DESCRIPTION).get()); + Set emptyTagList = new HashSet<>(); - Task task = new Task(description); + Task task = new Task(description, false, emptyTagList); return new AddTaskCommand(task); } diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 8b9ce978842..80d2ce45650 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -10,7 +10,9 @@ public class CliSyntax { public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_CONTACT = new Prefix("c/"); + public static final Prefix PREFIX_TASK = new Prefix("t/"); + public static final Prefix PREFIX_TAG = new Prefix("l/"); public static final Prefix PREFIX_TASK_DESCRIPTION = new Prefix("d/"); public static final Prefix PREFIX_TASK_DEADLINE = new Prefix("D/"); diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 3bd9de08211..f2e13abbb55 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -97,6 +97,22 @@ public void removePerson(Person key) { persons.remove(key); } + //// task-level operations + + public void setTask(Task target, Task editedTask) { + requireNonNull(editedTask); + + tasks.setTask(target, editedTask); + } + + /** + * Returns true if a task with the same identity as {@code task} exists in the address book. + */ + public boolean hasTask(Task task) { + requireNonNull(task); + return tasks.contains(task); + } + //// util methods @Override diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index e916ee040f9..6a5db74b038 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -80,6 +80,10 @@ public interface Model { */ void setPerson(Person target, Person editedPerson); + void setTask(Task target, Task editedTask); + + boolean hasTask(Task task); + /** Returns an unmodifiable view of the filtered person list */ ObservableList getFilteredPersonList(); diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index c81028ebf69..19f20ef6951 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -115,6 +115,19 @@ public void setPerson(Person target, Person editedPerson) { addressBook.setPerson(target, editedPerson); } + @Override + public void setTask(Task target, Task editedTask) { + requireAllNonNull(target, editedTask); + + addressBook.setTask(target, editedTask); + } + + @Override + public boolean hasTask(Task task) { + requireNonNull(task); + return addressBook.hasTask(task); + } + //=========== Filtered Person List Accessors ============================================================= /** diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java index cffdd450b07..102ed670b85 100644 --- a/src/main/java/seedu/address/model/task/Task.java +++ b/src/main/java/seedu/address/model/task/Task.java @@ -1,21 +1,44 @@ package seedu.address.model.task; +import seedu.address.model.tag.Tag; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + /** * Represents a Task in the TaskList. */ public class Task { private Description description; private boolean isDone; + private final Set tags = new HashSet<>(); /** * A constructor that creates an instance of Task. * @param description The description of the task. */ - public Task(Description description) { - this.isDone = false; + public Task(Description description, boolean isDone, Set tags) { + requireAllNonNull(description, isDone, tags); + this.isDone = isDone; this.description = description; + this.tags.addAll(tags); + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); } + public Description getDescription() { return this.description; } + + public boolean getIsDone() { return this.isDone; } + /** * Returns true if task is done, false if task is not done. * @return boolean indicating task completion status. @@ -37,4 +60,13 @@ public void markTask() { public void unmarkTask() { isDone = false; } + + public boolean isSameTask(Task otherTask) { + if (otherTask == this) { + return true; + } + + return otherTask != null + && otherTask.getDescription().equals(getDescription()); + } } diff --git a/src/main/java/seedu/address/model/task/TaskList.java b/src/main/java/seedu/address/model/task/TaskList.java index eb6a9f18341..88176614f8a 100644 --- a/src/main/java/seedu/address/model/task/TaskList.java +++ b/src/main/java/seedu/address/model/task/TaskList.java @@ -1,6 +1,7 @@ package seedu.address.model.task; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.util.Iterator; @@ -39,4 +40,32 @@ public String addTask(Task task) { public Iterator iterator() { return internalList.iterator(); } + + /** + * Replaces the person {@code target} in the list with {@code editedTask}. + * {@code target} must exist in the list. + * The person identity of {@code editedTask} must not be the same as another existing person in the list. + */ + public void setTask(Task target, Task editedTask) { + requireAllNonNull(target, editedTask); + + int index = internalList.indexOf(target); +// if (index == -1) { +// throw new TaskNotFoundException(); +// } +// +// if (!target.isSameTask(editedTask) && contains(editedTask)) { +// throw new DuplicateTaskException(); +// } + + internalList.set(index, editedTask); + } + + /** + * Returns true if the list contains an equivalent person as the given argument. + */ + public boolean contains(Task toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameTask); + } } From ed53f0c7938da2a3848cd9531ef4beb2483629ed Mon Sep 17 00:00:00 2001 From: junwei-tan Date: Sun, 9 Oct 2022 13:11:04 +0800 Subject: [PATCH 02/31] Add find task command --- .../seedu/address/commons/core/Messages.java | 1 + .../logic/commands/FindTaskCommand.java | 45 +++++++++++++ .../logic/parser/AddressBookParser.java | 4 ++ .../logic/parser/FindTaskCommandParser.java | 66 +++++++++++++++++++ .../address/logic/parser/ParserUtil.java | 55 +++++++++++++++- .../seedu/address/model/task/Deadline.java | 49 ++++++++++---- .../seedu/address/model/task/Description.java | 2 +- .../java/seedu/address/model/task/Task.java | 20 ++++++ .../task/TaskContainsKeywordsPredicate.java | 51 ++++++++++++++ 9 files changed, 277 insertions(+), 16 deletions(-) create mode 100644 src/main/java/seedu/address/logic/commands/FindTaskCommand.java create mode 100644 src/main/java/seedu/address/logic/parser/FindTaskCommandParser.java create mode 100644 src/main/java/seedu/address/model/task/TaskContainsKeywordsPredicate.java diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index d9fdb47dfa0..93a2bd37dbe 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -11,4 +11,5 @@ public class Messages { public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; public static final String MESSAGE_INVALID_TASK_DISPLAYED_INDEX = "The task index provided is invalid"; + public static final String MESSAGE_TASKS_LISTED_OVERVIEW = "%1$d tasks listed!"; } diff --git a/src/main/java/seedu/address/logic/commands/FindTaskCommand.java b/src/main/java/seedu/address/logic/commands/FindTaskCommand.java new file mode 100644 index 00000000000..0ca6b965dc8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindTaskCommand.java @@ -0,0 +1,45 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.Messages; +import seedu.address.model.Model; +import seedu.address.model.task.TaskContainsKeywordsPredicate; + +/** + * Finds and lists all tasks in address book whose description and/or deadline + * contains any of the argument keywords. + * Keyword matching is case-insensitive. + */ +public class FindTaskCommand extends Command { + + public static final String COMMAND_WORD = "findT"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Finds all tasks whose description/deadline contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " homework assignment"; + + private final TaskContainsKeywordsPredicate predicate; + + public FindTaskCommand(TaskContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredTaskList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_TASKS_LISTED_OVERVIEW, + model.getFilteredTaskList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindTaskCommand // instanceof handles nulls + && predicate.equals(((FindTaskCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 84010dac096..4464adec616 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -15,6 +15,7 @@ import seedu.address.logic.commands.EditContactCommand; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.FindContactCommand; +import seedu.address.logic.commands.FindTaskCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListContactCommand; import seedu.address.logic.commands.ListTaskCommand; @@ -82,6 +83,9 @@ public Command parseCommand(String userInput) throws ParseException { case DeleteTagCommand.COMMAND_WORD: return new DeleteTagCommandParser().parse(arguments); + case FindTaskCommand.COMMAND_WORD: + return new FindTaskCommandParser().parse(arguments); + case MarkTaskCommand.COMMAND_WORD: return new MarkTaskCommandParser().parse(arguments); diff --git a/src/main/java/seedu/address/logic/parser/FindTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/FindTaskCommandParser.java new file mode 100644 index 00000000000..90172a0f9ea --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindTaskCommandParser.java @@ -0,0 +1,66 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DESCRIPTION; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import seedu.address.logic.commands.FindTaskCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.task.Deadline; +import seedu.address.model.task.Description; +import seedu.address.model.task.TaskContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindTaskCommandParser object + */ +public class FindTaskCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindTaskCommandParser + * and returns a FindTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindTaskCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TASK_DESCRIPTION, PREFIX_TASK_DEADLINE); + + if (arePrefixesEmpty(argMultimap, PREFIX_TASK_DESCRIPTION, PREFIX_TASK_DEADLINE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FindTaskCommand.MESSAGE_USAGE)); + } + + List descriptions = getDescriptions(argMultimap); + List deadlines = getDeadlines(argMultimap); + + return new FindTaskCommand( + new TaskContainsKeywordsPredicate(descriptions, deadlines)); + } + + private List getDescriptions(ArgumentMultimap argMultimap) throws ParseException { + if (argMultimap.getValue(PREFIX_TASK_DESCRIPTION).isEmpty()) { + return new ArrayList<>(); + } + return ParserUtil.parseDescriptions(argMultimap.getValue(PREFIX_TASK_DESCRIPTION).get()); + } + + private List getDeadlines(ArgumentMultimap argMultimap) throws ParseException { + if (argMultimap.getValue(PREFIX_TASK_DEADLINE).isEmpty()) { + return new ArrayList<>(); + } + return ParserUtil.parseDeadlines(argMultimap.getValue(PREFIX_TASK_DEADLINE).get()); + } + + /** + * Returns true if all the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesEmpty(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).noneMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 508736647c7..554878d8dc1 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -16,6 +16,7 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Deadline; import seedu.address.model.task.Description; /** @@ -57,6 +58,8 @@ public static Name parseName(String name) throws ParseException { * Parses a string of {@code String names} into a list of {@code String names}. * Leading and trailing whitespaces will be trimmed. * + * @param names The names to be parsed. + * @return A list of names. * @throws ParseException if any {@code name} is invalid. */ public static List parseNames(String names) throws ParseException { @@ -92,6 +95,8 @@ public static Phone parsePhone(String phone) throws ParseException { * Parses a string of {@code String phones} into a list of {@code String phones}. * Leading and trailing whitespaces will be trimmed. * + * @param phones The phones to be parsed. + * @return A list of phones. * @throws ParseException if any {@code phone} is invalid. */ public static List parsePhones(String phones) throws ParseException { @@ -127,6 +132,8 @@ public static Address parseAddress(String address) throws ParseException { * Parses a string of {@code String addresses} into a list of {@code String addresses}. * Leading and trailing whitespaces will be trimmed. * + * @param addresses The addresses to be parsed. + * @return A list of addresses. * @throws ParseException if any {@code address} is invalid. */ public static List
parseAddresses(String addresses) throws ParseException { @@ -162,6 +169,8 @@ public static Email parseEmail(String email) throws ParseException { * Parses a string of {@code String emails} into a list of {@code String emails}. * Leading and trailing whitespaces will be trimmed. * + * @param emails The emails to be parsed. + * @return A list of emails. * @throws ParseException if any {@code email} is invalid. */ public static List parseEmails(String emails) throws ParseException { @@ -215,8 +224,52 @@ public static Description parseDescription(String description) throws ParseExcep requireNonNull(description); String trimmedDescription = description.trim(); if (!Description.isValidDescription(trimmedDescription)) { - throw new ParseException(Email.MESSAGE_CONSTRAINTS); + throw new ParseException(Description.MESSAGE_CONSTRAINTS); } return new Description(trimmedDescription); } + + /** + * Parses a string of {@code String descriptions} into a list of {@code String descriptions}. + * Leading and trailing whitespaces will be trimmed. + * + * @param descriptions The descriptions to be parsed. + * @return A list of descriptions. + * @throws ParseException if any {@code description} is invalid. + */ + public static List parseDescriptions(String descriptions) throws ParseException { + requireNonNull(descriptions); + String trimmedDescriptions = descriptions.trim(); + String[] descriptionArr = trimmedDescriptions.split(" "); + ArrayList descriptionList = new ArrayList<>(); + for (String description : descriptionArr) { + if (!Description.isValidDescription(description)) { + throw new ParseException(Description.MESSAGE_CONSTRAINTS); + } + descriptionList.add(new Description(description)); + } + return descriptionList; + } + + /** + * Parses a string of {@code String deadlines} into a list of {@code String deadlines}. + * Leading and trailing whitespaces will be trimmed. + * + * @param deadlines The descriptions to be parsed. + * @return A list of deadlines. + * @throws ParseException if any {@code description} is invalid. + */ + public static List parseDeadlines(String deadlines) throws ParseException { + requireNonNull(deadlines); + String trimmedDeadlines = deadlines.trim(); + String[] deadlineArr = trimmedDeadlines.split(" "); + ArrayList descriptionList = new ArrayList<>(); + for (String deadline : deadlineArr) { + if (!Deadline.isValidDeadline(deadline)) { + throw new ParseException(Deadline.MESSAGE_CONSTRAINTS); + } + descriptionList.add(new Deadline(deadline)); + } + return descriptionList; + } } diff --git a/src/main/java/seedu/address/model/task/Deadline.java b/src/main/java/seedu/address/model/task/Deadline.java index 960e01ead90..d6db54fbb6a 100644 --- a/src/main/java/seedu/address/model/task/Deadline.java +++ b/src/main/java/seedu/address/model/task/Deadline.java @@ -1,29 +1,50 @@ package seedu.address.model.task; import static java.util.Objects.requireNonNull; - -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import static seedu.address.commons.util.AppUtil.checkArgument; /** * Represents the deadline of a task in the TaskList. */ -public class Deadline extends Command { - // Implement later. - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); +public class Deadline { + // TODO: implement this class + // TODO: edit MESSAGE_CONSTRAINTS + public static final String MESSAGE_CONSTRAINTS = " "; + + + // TODO: Edit VALIDATION_REGEX (Copied from Description class for now) + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + // TODO: Implement deadline (e.g. DateTime... ) + private final String deadline; + + // TODO: Implement constructor + /** + * Constructs an {@code Deadline}. + * + * @param deadline A valid deadline. + */ + public Deadline(String deadline) { + requireNonNull(deadline); + + checkArgument(isValidDeadline(deadline), MESSAGE_CONSTRAINTS); + this.deadline = deadline; + } - return null; + // TODO: Implement isValidDeadline method + /** + * Returns true if a given string is a valid description. + */ + public static boolean isValidDeadline(String test) { + return true; } @Override public boolean equals(Object other) { return true; } - } - diff --git a/src/main/java/seedu/address/model/task/Description.java b/src/main/java/seedu/address/model/task/Description.java index f88a7331791..79fb5370a29 100644 --- a/src/main/java/seedu/address/model/task/Description.java +++ b/src/main/java/seedu/address/model/task/Description.java @@ -16,7 +16,7 @@ public class Description { */ public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; - public final String taskDescription; + private final String taskDescription; /** * Constructs an {@code Description}. diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java index cffdd450b07..11abd4f2302 100644 --- a/src/main/java/seedu/address/model/task/Task.java +++ b/src/main/java/seedu/address/model/task/Task.java @@ -5,8 +5,10 @@ */ public class Task { private Description description; + private Deadline deadline; private boolean isDone; + /** * A constructor that creates an instance of Task. * @param description The description of the task. @@ -14,6 +16,24 @@ public class Task { public Task(Description description) { this.isDone = false; this.description = description; + // TODO: Edit after implementing Deadline class + this.deadline = new Deadline(""); + } + + /** + * Returns description of task. + * @return Description Details of task. + */ + public Description getDescription() { + return description; + } + + /** + * Returns deadline of task. + * @return deadline Deadline of task. + */ + public Deadline getDeadline() { + return deadline; } /** diff --git a/src/main/java/seedu/address/model/task/TaskContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/task/TaskContainsKeywordsPredicate.java new file mode 100644 index 00000000000..e5b7ac8dce7 --- /dev/null +++ b/src/main/java/seedu/address/model/task/TaskContainsKeywordsPredicate.java @@ -0,0 +1,51 @@ +package seedu.address.model.task; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code task}'s {@code Description} and/or {@code Deadline} + * matches any of the keywords given. + */ +public class TaskContainsKeywordsPredicate implements Predicate { + private final List descriptionKeywords; + private final List deadlineKeywords; + + /** + * Constructs an {@code TaskContainsKeywordsPredicate}. + * + * @param descriptionKeywords A list containing keywords for {@code Description}. + * @param deadlineKeywords A list containing keywords for {@code Deadline}. + */ + public TaskContainsKeywordsPredicate(List descriptionKeywords, + List deadlineKeywords) { + this.descriptionKeywords = descriptionKeywords; + this.deadlineKeywords = deadlineKeywords; + } + + @Override + public boolean test(Task task) { + return (descriptionKeywords.isEmpty() || descriptionKeywords.stream().anyMatch(keyword -> + StringUtil.containsWordIgnoreCase(task.getDescription().toString(), keyword.toString()))) + && (deadlineKeywords.isEmpty() || deadlineKeywords.stream().anyMatch(keyword -> + StringUtil.containsWordIgnoreCase(task.getDeadline().toString(), keyword.toString()))); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof TaskContainsKeywordsPredicate)) { + return false; + } + + TaskContainsKeywordsPredicate castedOther = (TaskContainsKeywordsPredicate) other; + + return descriptionKeywords.equals(castedOther.descriptionKeywords) + && deadlineKeywords.equals(castedOther.deadlineKeywords); + } +} From 3a1190d7bed097eed8fbeada1b0c46754abc8cd9 Mon Sep 17 00:00:00 2001 From: junwei-tan Date: Sun, 9 Oct 2022 15:12:55 +0800 Subject: [PATCH 03/31] Format code for code style --- .../java/seedu/address/logic/parser/AddressBookParser.java | 1 - .../address/logic/parser/task/FindTaskCommandParser.java | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index bc8bba2a6ff..4eba5241121 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -9,7 +9,6 @@ import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.ExitCommand; - import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.contact.AddContactCommand; import seedu.address.logic.commands.contact.DeleteContactCommand; diff --git a/src/main/java/seedu/address/logic/parser/task/FindTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/task/FindTaskCommandParser.java index 73552e86c27..88ed1029e69 100644 --- a/src/main/java/seedu/address/logic/parser/task/FindTaskCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/task/FindTaskCommandParser.java @@ -9,7 +9,11 @@ import java.util.stream.Stream; import seedu.address.logic.commands.task.FindTaskCommand; -import seedu.address.logic.parser.*; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.task.Deadline; import seedu.address.model.task.Description; From cf1b9368497429d35465b7b29ea82e0fdeec2899 Mon Sep 17 00:00:00 2001 From: alvintfl Date: Sun, 9 Oct 2022 15:40:34 +0800 Subject: [PATCH 04/31] Add edit command for tasks There is no way for the user to edit their tasks after adding it to the task list. Let's add an edit command to allow the user to do so. --- .../logic/commands/task/EditTaskCommand.java | 181 ++++++++++++++++++ .../logic/parser/AddressBookParser.java | 11 +- .../parser/task/EditTaskCommandParser.java | 77 ++++++++ .../java/seedu/address/model/AddressBook.java | 19 ++ src/main/java/seedu/address/model/Model.java | 12 ++ .../seedu/address/model/ModelManager.java | 13 ++ .../java/seedu/address/model/task/Task.java | 33 ++++ .../seedu/address/model/task/TaskList.java | 31 +++ .../exceptions/DuplicateTaskException.java | 11 ++ .../exceptions/TaskNotFoundException.java | 6 + .../logic/commands/AddContactCommandTest.java | 10 + 11 files changed, 401 insertions(+), 3 deletions(-) create mode 100644 src/main/java/seedu/address/logic/commands/task/EditTaskCommand.java create mode 100644 src/main/java/seedu/address/logic/parser/task/EditTaskCommandParser.java create mode 100644 src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java create mode 100644 src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java diff --git a/src/main/java/seedu/address/logic/commands/task/EditTaskCommand.java b/src/main/java/seedu/address/logic/commands/task/EditTaskCommand.java new file mode 100644 index 00000000000..c43b829b354 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/task/EditTaskCommand.java @@ -0,0 +1,181 @@ +package seedu.address.logic.commands.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DESCRIPTION; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.tag.Tag; +import seedu.address.model.task.Description; +import seedu.address.model.task.Task; + +/** + * Edits the details of an existing task in the task list. + */ +public class EditTaskCommand extends Command { + + public static final String COMMAND_WORD = "editT"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the task identified " + + "by the index number used in the displayed task list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_TASK_DESCRIPTION + "DESCRIPTION] " + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_TASK_DESCRIPTION + "go sleep"; + + public static final String MESSAGE_EDIT_TASK_SUCCESS = "Edited Task: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in the task list."; + + private final Index index; + private final EditTaskCommand.EditTaskDescriptor editTaskDescriptor; + + /** + * @param index of the person in the filtered person list to edit + * @param editTaskDescriptor details to edit the person with + */ + public EditTaskCommand(Index index, EditTaskCommand.EditTaskDescriptor editTaskDescriptor) { + requireNonNull(index); + requireNonNull(editTaskDescriptor); + + this.index = index; + this.editTaskDescriptor = new EditTaskCommand.EditTaskDescriptor(editTaskDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Task taskToEdit = lastShownList.get(index.getZeroBased()); + Task editedTask = createEditedTask(taskToEdit, editTaskDescriptor); + + if (!taskToEdit.isSameTask(editedTask) && model.hasTask(editedTask)) { + throw new CommandException(MESSAGE_DUPLICATE_TASK); + } + + model.setTask(taskToEdit, editedTask); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + return new CommandResult(String.format(MESSAGE_EDIT_TASK_SUCCESS, editedTask)); + } + + /** + * Creates and returns a {@code Task} with the details of {@code taskToEdit} + * edited with {@code editTaskDescriptor}. + */ + private static Task createEditedTask(Task taskToEdit, EditTaskCommand.EditTaskDescriptor editTaskDescriptor) { + assert taskToEdit != null; + + Description updatedDescription = editTaskDescriptor.getDescription().orElse(taskToEdit.getDescription()); + //TODO Copy tags to edited Task + // Set updatedTags = editTaskDescriptor.getTags().orElse(taskToEdit.getTags()); + + return new Task(updatedDescription); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditTaskCommand)) { + return false; + } + + // state check + EditTaskCommand e = (EditTaskCommand) other; + return index.equals(e.index) + && editTaskDescriptor.equals(e.editTaskDescriptor); + } + + /** + * Stores the details to edit the task with. Each non-empty field value will replace the + * corresponding field value of the person. + */ + public static class EditTaskDescriptor { + private Description description; + private Set tags; + + public EditTaskDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditTaskDescriptor(EditTaskCommand.EditTaskDescriptor toCopy) { + setDescription(toCopy.description); + setTags(toCopy.tags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(description, tags); + } + + public void setDescription(Description description) { + this.description = description; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditTaskCommand.EditTaskDescriptor)) { + return false; + } + + // state check + EditTaskCommand.EditTaskDescriptor e = (EditTaskCommand.EditTaskDescriptor) other; + + return getDescription().equals(e.getDescription()) + && getTags().equals(e.getTags()); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 14380611554..847899baa13 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -17,6 +17,7 @@ import seedu.address.logic.commands.contact.ListContactCommand; import seedu.address.logic.commands.tag.AddTagCommand; import seedu.address.logic.commands.tag.DeleteTagCommand; +import seedu.address.logic.commands.task.EditTaskCommand; import seedu.address.logic.commands.task.ListTaskCommand; import seedu.address.logic.commands.task.MarkTaskCommand; import seedu.address.logic.commands.task.UnmarkTaskCommand; @@ -27,6 +28,7 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.logic.parser.tag.AddTagCommandParser; import seedu.address.logic.parser.tag.DeleteTagCommandParser; +import seedu.address.logic.parser.task.EditTaskCommandParser; import seedu.address.logic.parser.task.MarkTaskCommandParser; import seedu.address.logic.parser.task.UnmarkTaskCommandParser; @@ -75,9 +77,6 @@ public Command parseCommand(String userInput) throws ParseException { case ListContactCommand.COMMAND_WORD: return new ListContactCommand(); - case ListTaskCommand.COMMAND_WORD: - return new ListTaskCommand(); - case ExitCommand.COMMAND_WORD: return new ExitCommand(); @@ -90,12 +89,18 @@ public Command parseCommand(String userInput) throws ParseException { case DeleteTagCommand.COMMAND_WORD: return new DeleteTagCommandParser().parse(arguments); + case EditTaskCommand.COMMAND_WORD: + return new EditTaskCommandParser().parse(arguments); + case MarkTaskCommand.COMMAND_WORD: return new MarkTaskCommandParser().parse(arguments); case UnmarkTaskCommand.COMMAND_WORD: return new UnmarkTaskCommandParser().parse(arguments); + case ListTaskCommand.COMMAND_WORD: + return new ListTaskCommand(); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } diff --git a/src/main/java/seedu/address/logic/parser/task/EditTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/task/EditTaskCommandParser.java new file mode 100644 index 00000000000..68f780894a7 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/task/EditTaskCommandParser.java @@ -0,0 +1,77 @@ +package seedu.address.logic.parser.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DESCRIPTION; + +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.task.EditTaskCommand; +import seedu.address.logic.commands.task.EditTaskCommand.EditTaskDescriptor; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; + +/** + * Parses input arguments and creates a new EditTaskCommand object + */ +public class EditTaskCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditTaskCommand + * and returns an EditTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditTaskCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TASK_DESCRIPTION); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTaskCommand.MESSAGE_USAGE), pe); + } + + EditTaskCommand.EditTaskDescriptor editTaskDescriptor = new EditTaskDescriptor(); + if (argMultimap.getValue(PREFIX_TASK_DESCRIPTION).isPresent()) { + editTaskDescriptor.setDescription( + ParserUtil.parseDescription(argMultimap.getValue(PREFIX_TASK_DESCRIPTION).get())); + } + parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editTaskDescriptor::setTags); + + if (!editTaskDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditTaskCommand.MESSAGE_NOT_EDITED); + } + + return new EditTaskCommand(index, editTaskDescriptor); + } + + /** + * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. + * If {@code tags} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero tags. + */ + private Optional> parseTagsForEdit(Collection tags) throws ParseException { + assert tags != null; + + if (tags.isEmpty()) { + return Optional.empty(); + } + Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; + return Optional.of(ParserUtil.parseTags(tagSet)); + } + +} + diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 3bd9de08211..512677b8aac 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -133,4 +133,23 @@ public ObservableList getTaskList() { public void addTask(Task task) { tasks.addTask(task); } + + /** + * Returns true if a task with the same description as {@code task} exists in the address book. + */ + public boolean hasTask(Task task) { + requireNonNull(task); + return tasks.contains(task); + } + + /** + * Replaces the given task {@code target} in the list with {@code editedTask}. + * {@code target} must exist in the address book. + * The task description of {@code editedTask} must not be the same as another existing task in the address book. + */ + public void setTask(Task target, Task editedTask) { + requireNonNull(editedTask); + + tasks.setTask(target, editedTask); + } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index e916ee040f9..46a092e3e34 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -95,6 +95,18 @@ public interface Model { */ void addT(Task task); + /** + * Returns true if a task with the same description as {@code task} exists in the address book. + */ + boolean hasTask(Task task); + + /** + * Replaces the given task {@code target} with {@code editedTask}. + * {@code target} must exist in the address book. + * The task description of {@code editedTask} must not be the same as another existing task in the address book. + */ + void setTask(Task target, Task editedTask); + /** Returns an unmodifiable view of the filtered task list */ ObservableList getFilteredTaskList(); diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index c81028ebf69..ecf4c876a58 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -158,6 +158,19 @@ public void addT(Task task) { addressBook.addTask(task); } + @Override + public boolean hasTask(Task task) { + requireNonNull(task); + return addressBook.hasTask(task); + } + + @Override + public void setTask(Task target, Task editedTask) { + requireAllNonNull(target, editedTask); + + addressBook.setTask(target, editedTask); + } + //=========== Filtered Task List Accessors ============================================================= /** diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java index cffdd450b07..0bfa765bc9d 100644 --- a/src/main/java/seedu/address/model/task/Task.java +++ b/src/main/java/seedu/address/model/task/Task.java @@ -1,11 +1,18 @@ package seedu.address.model.task; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import seedu.address.model.tag.Tag; + /** * Represents a Task in the TaskList. */ public class Task { private Description description; private boolean isDone; + private final Set tags = new HashSet<>(); /** * A constructor that creates an instance of Task. @@ -16,6 +23,10 @@ public Task(Description description) { this.description = description; } + public Description getDescription() { + return description; + } + /** * Returns true if task is done, false if task is not done. * @return boolean indicating task completion status. @@ -24,6 +35,14 @@ public boolean getTaskStatus() { return isDone; } + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + /** * Marks task as done. */ @@ -37,4 +56,18 @@ public void markTask() { public void unmarkTask() { isDone = false; } + + /** + * Returns true if both tasks have the same description. + * This defines a weaker notion of equality between two tasks. + */ + public boolean isSameTask(Task otherTask) { + if (otherTask == this) { + return true; + } + + return otherTask != null + && otherTask.getDescription().equals(getDescription()); + } + } diff --git a/src/main/java/seedu/address/model/task/TaskList.java b/src/main/java/seedu/address/model/task/TaskList.java index eb6a9f18341..e75eab19207 100644 --- a/src/main/java/seedu/address/model/task/TaskList.java +++ b/src/main/java/seedu/address/model/task/TaskList.java @@ -1,11 +1,14 @@ package seedu.address.model.task; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.util.Iterator; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import seedu.address.model.task.exceptions.DuplicateTaskException; +import seedu.address.model.task.exceptions.TaskNotFoundException; /** * Represents the tasklist. @@ -35,6 +38,34 @@ public String addTask(Task task) { return TaskUi.addText(internalList.get(internalList.size() - 1).toString(), internalList.size()); } + /** + * Replaces the task {@code target} in the list with {@code editedTask}. + * {@code target} must exist in the list. + * The task description of {@code editedTask} must not be the same as another existing task in the list. + */ + public void setTask(Task target, Task editedTask) { + requireAllNonNull(target, editedTask); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new TaskNotFoundException(); + } + + if (!target.isSameTask(editedTask) && contains(editedTask)) { + throw new DuplicateTaskException(); + } + + internalList.set(index, editedTask); + } + + /** + * Returns true if the list contains an equivalent task as the given argument. + */ + public boolean contains(Task toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameTask); + } + @Override public Iterator iterator() { return internalList.iterator(); diff --git a/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java b/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java new file mode 100644 index 00000000000..e7a3033b292 --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java @@ -0,0 +1,11 @@ +package seedu.address.model.task.exceptions; + +/** + * Signals that the operation will result in duplicate Tasks (Tasks are considered duplicates if they have the same + * description). + */ +public class DuplicateTaskException extends RuntimeException { + public DuplicateTaskException() { + super("Operation would result in duplicate tasks"); + } +} diff --git a/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java new file mode 100644 index 00000000000..8d122a5692c --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.task.exceptions; + +/** + * Signals that the operation is unable to find the specified task. + */ +public class TaskNotFoundException extends RuntimeException {} diff --git a/src/test/java/seedu/address/logic/commands/AddContactCommandTest.java b/src/test/java/seedu/address/logic/commands/AddContactCommandTest.java index 5c9a38b958b..80cbca554e6 100644 --- a/src/test/java/seedu/address/logic/commands/AddContactCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddContactCommandTest.java @@ -156,6 +156,16 @@ public void addT(Task task) { throw new AssertionError("This method should not be called."); } + @Override + public boolean hasTask(Task task) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void setTask(Task target, Task editedTask) { + throw new AssertionError("This method should not be called."); + } + public ObservableList getFilteredTaskList() { throw new AssertionError("This method should not be called."); } From 3b9a7f1753396e6c7f17142fa07bae3fdd42a83c Mon Sep 17 00:00:00 2001 From: ryanguai Date: Sun, 9 Oct 2022 18:21:24 +0800 Subject: [PATCH 05/31] Fix checkstyle problems and add two Task exceptions --- .../logic/commands/tag/AddTagCommand.java | 3 ++- .../logic/parser/tag/AddTagCommandParser.java | 4 +++- .../java/seedu/address/model/task/Task.java | 17 +++++++++++++---- .../java/seedu/address/model/task/TaskList.java | 16 +++++++++------- .../task/exceptions/DuplicateTaskException.java | 11 +++++++++++ .../task/exceptions/TaskNotFoundException.java | 6 ++++++ 6 files changed, 44 insertions(+), 13 deletions(-) create mode 100644 src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java create mode 100644 src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java diff --git a/src/main/java/seedu/address/logic/commands/tag/AddTagCommand.java b/src/main/java/seedu/address/logic/commands/tag/AddTagCommand.java index e4edd522eff..480ade79a77 100644 --- a/src/main/java/seedu/address/logic/commands/tag/AddTagCommand.java +++ b/src/main/java/seedu/address/logic/commands/tag/AddTagCommand.java @@ -57,7 +57,8 @@ public class AddTagCommand extends Command { * @param index of the person in the filtered person list to edit * @param editPersonDescriptor details to edit the person with */ - public AddTagCommand(Index index, EditPersonDescriptor editPersonDescriptor, boolean addTagToContact, boolean addTagToTask, EditTaskDescriptor editTaskDescriptor) { + public AddTagCommand(Index index, EditPersonDescriptor editPersonDescriptor, boolean addTagToContact, + boolean addTagToTask, EditTaskDescriptor editTaskDescriptor) { requireNonNull(index); requireNonNull(editPersonDescriptor); diff --git a/src/main/java/seedu/address/logic/parser/tag/AddTagCommandParser.java b/src/main/java/seedu/address/logic/parser/tag/AddTagCommandParser.java index f8bdbc20f7b..719f3edc560 100644 --- a/src/main/java/seedu/address/logic/parser/tag/AddTagCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/tag/AddTagCommandParser.java @@ -2,7 +2,9 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.*; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONTACT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK; import java.util.Collection; import java.util.Collections; diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java index 102ed670b85..109b71feaf0 100644 --- a/src/main/java/seedu/address/model/task/Task.java +++ b/src/main/java/seedu/address/model/task/Task.java @@ -1,13 +1,13 @@ package seedu.address.model.task; -import seedu.address.model.tag.Tag; - import java.util.Collections; import java.util.HashSet; import java.util.Set; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import seedu.address.model.tag.Tag; + /** * Represents a Task in the TaskList. */ @@ -35,9 +35,13 @@ public Set getTags() { return Collections.unmodifiableSet(tags); } - public Description getDescription() { return this.description; } + public Description getDescription() { + return this.description; + } - public boolean getIsDone() { return this.isDone; } + public boolean getIsDone() { + return this.isDone; + } /** * Returns true if task is done, false if task is not done. @@ -61,6 +65,11 @@ public void unmarkTask() { isDone = false; } + /** + * Returns true if both tasks have matching descriptions, false otherwise. + * @param otherTask Another task. + * @return boolean indicating whether tasks are the same. + */ public boolean isSameTask(Task otherTask) { if (otherTask == this) { return true; diff --git a/src/main/java/seedu/address/model/task/TaskList.java b/src/main/java/seedu/address/model/task/TaskList.java index 88176614f8a..dc002ffa04c 100644 --- a/src/main/java/seedu/address/model/task/TaskList.java +++ b/src/main/java/seedu/address/model/task/TaskList.java @@ -7,6 +7,8 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import seedu.address.model.task.exceptions.DuplicateTaskException; +import seedu.address.model.task.exceptions.TaskNotFoundException; /** * Represents the tasklist. @@ -50,13 +52,13 @@ public void setTask(Task target, Task editedTask) { requireAllNonNull(target, editedTask); int index = internalList.indexOf(target); -// if (index == -1) { -// throw new TaskNotFoundException(); -// } -// -// if (!target.isSameTask(editedTask) && contains(editedTask)) { -// throw new DuplicateTaskException(); -// } + if (index == -1) { + throw new TaskNotFoundException(); + } + + if (!target.isSameTask(editedTask) && contains(editedTask)) { + throw new DuplicateTaskException(); + } internalList.set(index, editedTask); } diff --git a/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java b/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java new file mode 100644 index 00000000000..9b7842f6534 --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java @@ -0,0 +1,11 @@ +package seedu.address.model.task.exceptions; + +/** + * Signals that the operation will result in duplicate Tasks (Tasks are considered duplicates if they have the same + * identity). + */ +public class DuplicateTaskException extends RuntimeException { + public DuplicateTaskException() { + super("Operation would result in duplicate tasks"); + } +} \ No newline at end of file diff --git a/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java new file mode 100644 index 00000000000..789eca38e69 --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.task.exceptions; + +/** + * Signals that the operation is unable to find the specified tag. + */ +public class TaskNotFoundException extends RuntimeException {} \ No newline at end of file From f23ddfa143e3453fab4d052ac0645a415c5e6fc5 Mon Sep 17 00:00:00 2001 From: ryanguai Date: Sun, 9 Oct 2022 18:22:59 +0800 Subject: [PATCH 06/31] Add newline at EOF for newly added exceptions --- .../address/model/task/exceptions/DuplicateTaskException.java | 2 +- .../address/model/task/exceptions/TaskNotFoundException.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java b/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java index 9b7842f6534..922c2a9cdd1 100644 --- a/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java +++ b/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java @@ -8,4 +8,4 @@ public class DuplicateTaskException extends RuntimeException { public DuplicateTaskException() { super("Operation would result in duplicate tasks"); } -} \ No newline at end of file +} diff --git a/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java index 789eca38e69..03ab2127d01 100644 --- a/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java +++ b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java @@ -3,4 +3,4 @@ /** * Signals that the operation is unable to find the specified tag. */ -public class TaskNotFoundException extends RuntimeException {} \ No newline at end of file +public class TaskNotFoundException extends RuntimeException {} From 2e5cea78e5026ee11c46844d19adc5d2866d3937 Mon Sep 17 00:00:00 2001 From: ryanguai Date: Sun, 9 Oct 2022 18:25:43 +0800 Subject: [PATCH 07/31] Correct checkstyle errors --- .../java/seedu/address/logic/commands/tag/AddTagCommand.java | 2 +- src/main/java/seedu/address/model/task/Task.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/seedu/address/logic/commands/tag/AddTagCommand.java b/src/main/java/seedu/address/logic/commands/tag/AddTagCommand.java index 480ade79a77..c36a1d6b3ec 100644 --- a/src/main/java/seedu/address/logic/commands/tag/AddTagCommand.java +++ b/src/main/java/seedu/address/logic/commands/tag/AddTagCommand.java @@ -58,7 +58,7 @@ public class AddTagCommand extends Command { * @param editPersonDescriptor details to edit the person with */ public AddTagCommand(Index index, EditPersonDescriptor editPersonDescriptor, boolean addTagToContact, - boolean addTagToTask, EditTaskDescriptor editTaskDescriptor) { + boolean addTagToTask, EditTaskDescriptor editTaskDescriptor) { requireNonNull(index); requireNonNull(editPersonDescriptor); diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java index 109b71feaf0..8d108d8b97b 100644 --- a/src/main/java/seedu/address/model/task/Task.java +++ b/src/main/java/seedu/address/model/task/Task.java @@ -1,11 +1,11 @@ package seedu.address.model.task; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + import java.util.Collections; import java.util.HashSet; import java.util.Set; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - import seedu.address.model.tag.Tag; /** From 075dac143e253c6df73088fa57033c49411e1102 Mon Sep 17 00:00:00 2001 From: ryanguai Date: Sun, 9 Oct 2022 18:44:33 +0800 Subject: [PATCH 08/31] Add override methods for model stub in test files --- .../address/logic/commands/AddContactCommandTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/seedu/address/logic/commands/AddContactCommandTest.java b/src/test/java/seedu/address/logic/commands/AddContactCommandTest.java index 5c9a38b958b..56eecf5a7e5 100644 --- a/src/test/java/seedu/address/logic/commands/AddContactCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddContactCommandTest.java @@ -141,6 +141,16 @@ public void setPerson(Person target, Person editedPerson) { throw new AssertionError("This method should not be called."); } + @Override + public void setTask(Task target, Task editedTask) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean hasTask(Task task) { + throw new AssertionError("This method should not be called."); + } + @Override public ObservableList getFilteredPersonList() { throw new AssertionError("This method should not be called."); From 414e66f1aa9724f797dc657c266117248c6c11c7 Mon Sep 17 00:00:00 2001 From: alvintfl Date: Tue, 11 Oct 2022 00:36:22 +0800 Subject: [PATCH 09/31] Add tabs There is no way to see other type of lists other than the list of contacts. Let's add tabs so that we can switch tabs to see another type of list. Tabs allow us to see the list that we are currently interested in at the moment. --- .../java/seedu/address/ui/MainWindow.java | 12 ++++--- .../java/seedu/address/ui/PersonListTab.java | 20 ++++++++++++ .../java/seedu/address/ui/TabContainer.java | 31 +++++++++++++++++++ src/main/resources/view/DarkTheme.css | 12 ++++++- src/main/resources/view/MainWindow.fxml | 6 ++-- src/main/resources/view/TabContainer.fxml | 6 ++++ 6 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 src/main/java/seedu/address/ui/PersonListTab.java create mode 100644 src/main/java/seedu/address/ui/TabContainer.java create mode 100644 src/main/resources/view/TabContainer.fxml diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..fddeb4965b7 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -31,9 +31,9 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; + private TabContainer tabContainer; @FXML private StackPane commandBoxPlaceholder; @@ -42,7 +42,7 @@ public class MainWindow extends UiPart { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane tabContainerPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -110,8 +110,10 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + PersonListTab personListTab = new PersonListTab(logic.getFilteredPersonList()); + + tabContainer = new TabContainer(personListTab); + tabContainerPlaceholder.getChildren().add(tabContainer.getRoot()); resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); @@ -163,9 +165,11 @@ private void handleExit() { primaryStage.hide(); } + /* TODO REMOVE public PersonListPanel getPersonListPanel() { return personListPanel; } + */ /** * Executes the command and returns the result. diff --git a/src/main/java/seedu/address/ui/PersonListTab.java b/src/main/java/seedu/address/ui/PersonListTab.java new file mode 100644 index 00000000000..5e6694414a4 --- /dev/null +++ b/src/main/java/seedu/address/ui/PersonListTab.java @@ -0,0 +1,20 @@ +package seedu.address.ui; + +import javafx.collections.ObservableList; +import javafx.scene.control.Tab; +import seedu.address.model.person.Person; + +/** + * Tab containing the list of persons. + */ +public class PersonListTab extends Tab { + private static final String TAB_NAME = "contacts"; + + /** + * Creates a {@code PersonListTab} with the given {@code ObservableList}. + */ + public PersonListTab(ObservableList personList) { + super(TAB_NAME, new PersonListPanel(personList).getRoot()); + getStyleClass().add("background"); + } +} diff --git a/src/main/java/seedu/address/ui/TabContainer.java b/src/main/java/seedu/address/ui/TabContainer.java new file mode 100644 index 00000000000..a4dca397c19 --- /dev/null +++ b/src/main/java/seedu/address/ui/TabContainer.java @@ -0,0 +1,31 @@ +package seedu.address.ui; + +import java.util.Arrays; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.layout.Region; + +/** + * Container containing a list of tabs. + */ +public class TabContainer extends UiPart { + + private static final String FXML = "TabContainer.fxml"; + + @FXML + private TabPane tabContainer; + + /** + * Creates a {@code TabContainer} with a given {@code Tab}. + * + * @param tabs Variable number of tabs + */ + public TabContainer(Tab... tabs) { + super(FXML); + ObservableList tabList = tabContainer.getTabs(); + tabList.addAll(Arrays.asList(tabs)); + } +} diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..a079f85c0b2 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -29,16 +29,26 @@ -fx-font-family: "Segoe UI Semibold"; } +.tab-label { + -fx-text-fill: white; + -fx-font-size: 13pt; +} + .tab-pane { -fx-padding: 0 0 0 1; } .tab-pane .tab-header-area { - -fx-padding: 0 0 0 0; + -fx-padding: 10 10 10 10; -fx-min-height: 0; -fx-max-height: 0; } +.tab-pane .tab-header-area .tab-header-background { + -fx-background-color: derive(#1d1d1d, 20%); + background-color: #383838; /* Used in the default.html file */ +} + .table-view { -fx-base: #1d1d1d; -fx-control-inner-background: #1d1d1d; diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index a431648f6c0..3414cc90510 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -46,12 +46,12 @@ - + - - + diff --git a/src/main/resources/view/TabContainer.fxml b/src/main/resources/view/TabContainer.fxml new file mode 100644 index 00000000000..f6491aedcd3 --- /dev/null +++ b/src/main/resources/view/TabContainer.fxml @@ -0,0 +1,6 @@ + + + + + From 1d8fd8e3f716ce98c117c1515173cc78d8e53a37 Mon Sep 17 00:00:00 2001 From: alvintfl Date: Tue, 11 Oct 2022 00:43:08 +0800 Subject: [PATCH 10/31] Remove line used to test tab style --- src/main/java/seedu/address/ui/PersonListTab.java | 1 - src/main/resources/view/DarkTheme.css | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/address/ui/PersonListTab.java b/src/main/java/seedu/address/ui/PersonListTab.java index 5e6694414a4..0e8dfd69fb3 100644 --- a/src/main/java/seedu/address/ui/PersonListTab.java +++ b/src/main/java/seedu/address/ui/PersonListTab.java @@ -15,6 +15,5 @@ public class PersonListTab extends Tab { */ public PersonListTab(ObservableList personList) { super(TAB_NAME, new PersonListPanel(personList).getRoot()); - getStyleClass().add("background"); } } diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index a079f85c0b2..583b0a02647 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -29,6 +29,11 @@ -fx-font-family: "Segoe UI Semibold"; } +.tab { + -fx-background-color: derive(#1d1d1d, 20%); + background-color: #383838; /* Used in the default.html file */ +} + .tab-label { -fx-text-fill: white; -fx-font-size: 13pt; From beae305045be9d49abbe7dc3fc46fd0eb1f2c68f Mon Sep 17 00:00:00 2001 From: alvintfl Date: Tue, 11 Oct 2022 09:55:09 +0800 Subject: [PATCH 11/31] Remove unused code --- src/main/java/seedu/address/ui/MainWindow.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index fddeb4965b7..f43eb6e4ac9 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -165,12 +165,6 @@ private void handleExit() { primaryStage.hide(); } - /* TODO REMOVE - public PersonListPanel getPersonListPanel() { - return personListPanel; - } - */ - /** * Executes the command and returns the result. * From 7c7cd3bd829173967d32b0211ba99e45c22d3246 Mon Sep 17 00:00:00 2001 From: junwei-tan Date: Wed, 12 Oct 2022 00:11:53 +0800 Subject: [PATCH 12/31] Refactor code Some classes violate the law of demeter. This is bad coding practice. Let's, * Edit "Task" class * Edit "TaskContainsKeywordsPredicate" class --- src/main/java/seedu/address/model/task/Task.java | 15 +++++++++++++++ .../model/task/TaskContainsKeywordsPredicate.java | 14 ++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java index 11abd4f2302..28f11957df4 100644 --- a/src/main/java/seedu/address/model/task/Task.java +++ b/src/main/java/seedu/address/model/task/Task.java @@ -1,5 +1,7 @@ package seedu.address.model.task; +import java.util.List; + /** * Represents a Task in the TaskList. */ @@ -57,4 +59,17 @@ public void markTask() { public void unmarkTask() { isDone = false; } + + /** + * Returns true if task contains any of the description or deadline keywords. + * By default, empty lists will return true. + * + * @param descriptionKeywords Possibly empty list containing keywords for {@code Description}. + * @param deadlineKeywords Possibly empty list containing keywords for {@code Deadline}. + * @return boolean indicating if task contains supplied keywords. + */ + public boolean containsKeywords(List descriptionKeywords, List deadlineKeywords) { + return (descriptionKeywords.isEmpty() || descriptionKeywords.contains(description)) + && (deadlineKeywords.isEmpty() || deadlineKeywords.contains(deadline)); + } } diff --git a/src/main/java/seedu/address/model/task/TaskContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/task/TaskContainsKeywordsPredicate.java index e5b7ac8dce7..55a4fac911a 100644 --- a/src/main/java/seedu/address/model/task/TaskContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/task/TaskContainsKeywordsPredicate.java @@ -3,8 +3,6 @@ import java.util.List; import java.util.function.Predicate; -import seedu.address.commons.util.StringUtil; - /** * Tests that a {@code task}'s {@code Description} and/or {@code Deadline} * matches any of the keywords given. @@ -25,12 +23,16 @@ public TaskContainsKeywordsPredicate(List descriptionKeywords, this.deadlineKeywords = deadlineKeywords; } + /** + * Returns true if task contains any of the description or deadline keywords. + * By default, empty keyword lists will return true. + * + * @param task Task that will be checked for matching keywords. + * @return boolean indicating if task contains supplied keywords. + */ @Override public boolean test(Task task) { - return (descriptionKeywords.isEmpty() || descriptionKeywords.stream().anyMatch(keyword -> - StringUtil.containsWordIgnoreCase(task.getDescription().toString(), keyword.toString()))) - && (deadlineKeywords.isEmpty() || deadlineKeywords.stream().anyMatch(keyword -> - StringUtil.containsWordIgnoreCase(task.getDeadline().toString(), keyword.toString()))); + return task.containsKeywords(descriptionKeywords, deadlineKeywords); } @Override From e175b35ca09535dbc2615cc1e96bd47511e11fcd Mon Sep 17 00:00:00 2001 From: alvintfl Date: Wed, 12 Oct 2022 00:23:23 +0800 Subject: [PATCH 13/31] Add GUI for task list There is no GUI to display the task list after adding tasks to it. Let's make a GUI to display the task list. Use of a list view and card allows the user to see tasks row by row. --- .../java/seedu/address/model/task/Task.java | 23 ++++++ .../java/seedu/address/ui/TagListTab.java | 19 +++++ src/main/java/seedu/address/ui/TaskCard.java | 72 +++++++++++++++++++ .../java/seedu/address/ui/TaskListPanel.java | 50 +++++++++++++ .../java/seedu/address/ui/TaskListTab.java | 19 +++++ src/main/resources/view/TaskListCard.fxml | 34 +++++++++ src/main/resources/view/TaskListPanel.fxml | 8 +++ 7 files changed, 225 insertions(+) create mode 100644 src/main/java/seedu/address/ui/TagListTab.java create mode 100644 src/main/java/seedu/address/ui/TaskCard.java create mode 100644 src/main/java/seedu/address/ui/TaskListPanel.java create mode 100644 src/main/java/seedu/address/ui/TaskListTab.java create mode 100644 src/main/resources/view/TaskListCard.fxml create mode 100644 src/main/resources/view/TaskListPanel.fxml diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java index cffdd450b07..79593529685 100644 --- a/src/main/java/seedu/address/model/task/Task.java +++ b/src/main/java/seedu/address/model/task/Task.java @@ -1,11 +1,18 @@ package seedu.address.model.task; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import seedu.address.model.tag.Tag; + /** * Represents a Task in the TaskList. */ public class Task { private Description description; private boolean isDone; + private final Set tags = new HashSet<>(); /** * A constructor that creates an instance of Task. @@ -16,6 +23,18 @@ public Task(Description description) { this.description = description; } + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + + public Description getDescription() { + return this.description; + } + /** * Returns true if task is done, false if task is not done. * @return boolean indicating task completion status. @@ -24,6 +43,10 @@ public boolean getTaskStatus() { return isDone; } + public String getStatusIcon() { + return (isDone ? "[X]" : "[ ]"); + } + /** * Marks task as done. */ diff --git a/src/main/java/seedu/address/ui/TagListTab.java b/src/main/java/seedu/address/ui/TagListTab.java new file mode 100644 index 00000000000..53eb122e9af --- /dev/null +++ b/src/main/java/seedu/address/ui/TagListTab.java @@ -0,0 +1,19 @@ +package seedu.address.ui; + +import javafx.collections.ObservableList; +import javafx.scene.control.Tab; +import seedu.address.model.tag.Tag; + +/** + * Tab containing the list of tags. + */ +public class TagListTab extends Tab { + private static final String TAB_NAME = "tags"; + + /** + * Creates a {@code TagListTab} with the given {@code ObservableList}. + */ + public TagListTab(ObservableList tagList) { + super(TAB_NAME, new TagListFlowPane(tagList).getRoot()); + } +} diff --git a/src/main/java/seedu/address/ui/TaskCard.java b/src/main/java/seedu/address/ui/TaskCard.java new file mode 100644 index 00000000000..98772d54e2f --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskCard.java @@ -0,0 +1,72 @@ + +package seedu.address.ui; + +import java.util.Comparator; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.task.Task; + +/** + * An UI component that displays information of a {@code Task}. + */ +public class TaskCard extends UiPart { + + private static final String FXML = "TaskListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Task task; + + @FXML + private HBox cardPane; + @FXML + private Label description; + @FXML + private Label id; + @FXML + private Label statusIcon; + @FXML + private FlowPane tags; + + /** + * Creates a {@code TaskCode} with the given {@code Task} and index to display. + */ + public TaskCard(Task task, int displayedIndex) { + super(FXML); + this.task = task; + id.setText(displayedIndex + ". "); + description.setText(task.getDescription().toString()); + statusIcon.setText(task.getStatusIcon()); + task.getTags().stream() + .sorted(Comparator.comparing(tag -> tag.tagName)) + .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TaskCard)) { + return false; + } + + // state check + TaskCard card = (TaskCard) other; + return id.getText().equals(card.id.getText()) + && task.equals(card.task); + } +} diff --git a/src/main/java/seedu/address/ui/TaskListPanel.java b/src/main/java/seedu/address/ui/TaskListPanel.java new file mode 100644 index 00000000000..37093ce70ee --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskListPanel.java @@ -0,0 +1,50 @@ + +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.task.Task; + +/** + * Panel containing the list of tasks. + */ +public class TaskListPanel extends UiPart { + private static final String FXML = "TaskListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(TaskListPanel.class); + + @FXML + private ListView taskListView; + + /** + * Creates a {@code TaskListPanel} with the given {@code ObservableList}. + */ + public TaskListPanel(ObservableList taskList) { + super(FXML); + taskListView.setItems(taskList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Task} using a {@code TaskCard}. + */ + class TaskListViewCell extends ListCell { + @Override + protected void updateItem(Task task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new TaskCard(task, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/TaskListTab.java b/src/main/java/seedu/address/ui/TaskListTab.java new file mode 100644 index 00000000000..1d8a7640193 --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskListTab.java @@ -0,0 +1,19 @@ +package seedu.address.ui; + +import javafx.collections.ObservableList; +import javafx.scene.control.Tab; +import seedu.address.model.task.Task; + +/** + * Tab containing the list of tasks. + */ +public class TaskListTab extends Tab { + private static final String TAB_NAME = "tasks"; + + /** + * Creates a {@code TaskListTab} with the given {@code ObservableList}. + */ + public TaskListTab(ObservableList taskList) { + super(TAB_NAME, new TaskListPanel(taskList).getRoot()); + } +} diff --git a/src/main/resources/view/TaskListCard.fxml b/src/main/resources/view/TaskListCard.fxml new file mode 100644 index 00000000000..210499ee352 --- /dev/null +++ b/src/main/resources/view/TaskListCard.fxml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/TaskListPanel.fxml b/src/main/resources/view/TaskListPanel.fxml new file mode 100644 index 00000000000..a58887c89d1 --- /dev/null +++ b/src/main/resources/view/TaskListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + From 5efb2d0800010e91cc2e4d41386e426828d62b44 Mon Sep 17 00:00:00 2001 From: alvintfl Date: Wed, 12 Oct 2022 00:25:49 +0800 Subject: [PATCH 14/31] Add GUI for tag list There is no GUI to display the tag list after adding a tag. Let's add a GUI to display the tag list. Use of a flow pane allows the user to add as many tags as they want without the tags exceeding the width of the pane. --- src/main/java/seedu/address/logic/Logic.java | 4 +++ .../seedu/address/logic/LogicManager.java | 6 ++++ .../java/seedu/address/model/AddressBook.java | 9 +++++ src/main/java/seedu/address/model/Model.java | 4 +++ .../seedu/address/model/ModelManager.java | 7 ++++ .../address/model/ReadOnlyAddressBook.java | 6 ++++ .../java/seedu/address/ui/MainWindow.java | 4 ++- .../seedu/address/ui/TagListFlowPane.java | 34 +++++++++++++++++++ src/main/resources/view/DarkTheme.css | 4 +-- src/main/resources/view/TagListFlowPane.fxml | 13 +++++++ .../logic/commands/AddContactCommandTest.java | 6 ++++ .../seedu/address/model/AddressBookTest.java | 6 ++++ 12 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 src/main/java/seedu/address/ui/TagListFlowPane.java create mode 100644 src/main/resources/view/TagListFlowPane.fxml diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 5e1f29acc2b..41908c5ce63 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -9,6 +9,7 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; import seedu.address.model.task.Task; /** @@ -37,6 +38,9 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of tasks */ ObservableList getFilteredTaskList(); + /** Returns an unmodifiable view of the filtered list of tags */ + ObservableList getTagList(); + /** * Returns the user prefs' address book file path. */ diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 3027a9b265f..98ca8acce32 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -15,6 +15,7 @@ import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; import seedu.address.model.task.Task; import seedu.address.storage.Storage; @@ -70,6 +71,11 @@ public ObservableList getFilteredTaskList() { return model.getFilteredTaskList(); } + @Override + public ObservableList getTagList() { + return model.getTagList(); + } + @Override public Path getAddressBookFilePath() { return model.getAddressBookFilePath(); diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 3bd9de08211..e8c1471f721 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -7,6 +7,8 @@ import javafx.collections.ObservableList; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; +import seedu.address.model.tag.Tag; +import seedu.address.model.tag.UniqueTagList; import seedu.address.model.task.Task; import seedu.address.model.task.TaskList; @@ -18,6 +20,7 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; private final TaskList tasks; + private final UniqueTagList tags; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -29,6 +32,7 @@ public class AddressBook implements ReadOnlyAddressBook { { persons = new UniquePersonList(); tasks = new TaskList(); + tags = new UniqueTagList(); } public AddressBook() {} @@ -133,4 +137,9 @@ public ObservableList getTaskList() { public void addTask(Task task) { tasks.addTask(task); } + + @Override + public ObservableList getTagList() { + return tags.asUnmodifiableObservableList(); + } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index e916ee040f9..d5561c8a5d3 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -6,6 +6,7 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; import seedu.address.model.task.Task; /** @@ -103,4 +104,7 @@ public interface Model { * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredTaskList(Predicate predicate); + + /** Returns an unmodifiable view of the filtered tag list */ + ObservableList getTagList(); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index c81028ebf69..c3ad5173f82 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -12,6 +12,7 @@ import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; import seedu.address.model.task.Task; @@ -174,4 +175,10 @@ public void updateFilteredTaskList(Predicate predicate) { requireNonNull(predicate); filteredTasks.setPredicate(predicate); } + + //=========== Tag List Accessors ============================================================= + @Override + public ObservableList getTagList() { + return addressBook.getTagList(); + } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index f3b412b4787..52536e812fa 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -2,6 +2,7 @@ import javafx.collections.ObservableList; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; import seedu.address.model.task.Task; /** @@ -20,6 +21,11 @@ public interface ReadOnlyAddressBook { */ ObservableList getTaskList(); + /** + * Returns an unmodifiable view of the tags list. + */ + ObservableList getTagList(); + /** * Adds a task to the TaskList * @param task diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index f43eb6e4ac9..286432567a4 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -111,8 +111,10 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { */ void fillInnerParts() { PersonListTab personListTab = new PersonListTab(logic.getFilteredPersonList()); + TaskListTab taskListTab = new TaskListTab(logic.getFilteredTaskList()); + TagListTab tagListTab = new TagListTab(logic.getTagList()); - tabContainer = new TabContainer(personListTab); + tabContainer = new TabContainer(personListTab, taskListTab, tagListTab); tabContainerPlaceholder.getChildren().add(tabContainer.getRoot()); resultDisplay = new ResultDisplay(); diff --git a/src/main/java/seedu/address/ui/TagListFlowPane.java b/src/main/java/seedu/address/ui/TagListFlowPane.java new file mode 100644 index 00000000000..57a71abbced --- /dev/null +++ b/src/main/java/seedu/address/ui/TagListFlowPane.java @@ -0,0 +1,34 @@ +package seedu.address.ui; + +import java.util.Comparator; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.tag.Tag; + +/** + * Flow pane containing the list of tags. + */ + +public class TagListFlowPane extends UiPart { + private static final String FXML = "TagListFlowPane.fxml"; + private final Logger logger = LogsCenter.getLogger(TagListFlowPane.class); + + @FXML + private FlowPane tags; + + /** + * Creates a {@code TagListFlowPane} with the given {@code ObservableList}. + */ + public TagListFlowPane(ObservableList tagList) { + super(FXML); + tagList.stream() + .sorted(Comparator.comparing(tag -> tag.tagName)) + .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + } +} diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 583b0a02647..d3015c3781c 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -36,7 +36,7 @@ .tab-label { -fx-text-fill: white; - -fx-font-size: 13pt; + -fx-font-size: 12pt; } .tab-pane { @@ -354,7 +354,7 @@ #tags { -fx-hgap: 7; - -fx-vgap: 3; + -fx-vgap: 7; } #tags .label { diff --git a/src/main/resources/view/TagListFlowPane.fxml b/src/main/resources/view/TagListFlowPane.fxml new file mode 100644 index 00000000000..aa575bd3847 --- /dev/null +++ b/src/main/resources/view/TagListFlowPane.fxml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/test/java/seedu/address/logic/commands/AddContactCommandTest.java b/src/test/java/seedu/address/logic/commands/AddContactCommandTest.java index 5c9a38b958b..a23a0e9115a 100644 --- a/src/test/java/seedu/address/logic/commands/AddContactCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddContactCommandTest.java @@ -22,6 +22,7 @@ import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; import seedu.address.model.task.Task; import seedu.address.testutil.PersonBuilder; @@ -156,6 +157,7 @@ public void addT(Task task) { throw new AssertionError("This method should not be called."); } + @Override public ObservableList getFilteredTaskList() { throw new AssertionError("This method should not be called."); } @@ -165,6 +167,10 @@ public void updateFilteredTaskList(Predicate predicate) { throw new AssertionError("This method should not be called."); } + @Override + public ObservableList getTagList() { + throw new AssertionError("This method should not be called."); + } } /** diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java index ef2c26bdd0a..2a520d8f04b 100644 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ b/src/test/java/seedu/address/model/AddressBookTest.java @@ -20,6 +20,7 @@ import javafx.collections.ObservableList; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.tag.Tag; import seedu.address.model.task.Task; import seedu.address.testutil.PersonBuilder; @@ -104,6 +105,11 @@ public ObservableList getTaskList() { throw new AssertionError("This method should not be called."); } + @Override + public ObservableList getTagList() { + throw new AssertionError("This method should not be called."); + } + @Override public void addTask(Task task) { throw new AssertionError("This method should not be called."); From e06a101b9cf5295b74b011dc76dba7ab5782de3d Mon Sep 17 00:00:00 2001 From: junwei-tan Date: Wed, 12 Oct 2022 02:33:29 +0800 Subject: [PATCH 15/31] Implement Deadline and Description class --- .../parser/task/AddTaskCommandParser.java | 10 ++- .../java/seedu/address/model/person/Name.java | 1 - .../seedu/address/model/task/Deadline.java | 47 +++++++---- .../seedu/address/model/task/Description.java | 10 ++- .../java/seedu/address/model/task/Task.java | 78 +++++++++++++++++-- 5 files changed, 117 insertions(+), 29 deletions(-) diff --git a/src/main/java/seedu/address/logic/parser/task/AddTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/task/AddTaskCommandParser.java index 28599890964..607e513356e 100644 --- a/src/main/java/seedu/address/logic/parser/task/AddTaskCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/task/AddTaskCommandParser.java @@ -4,6 +4,8 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DEADLINE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DESCRIPTION; +import java.util.HashSet; +import java.util.Set; import java.util.stream.Stream; import seedu.address.logic.commands.task.AddTaskCommand; @@ -13,6 +15,8 @@ import seedu.address.logic.parser.ParserUtil; import seedu.address.logic.parser.Prefix; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.tag.Tag; +import seedu.address.model.task.Deadline; import seedu.address.model.task.Description; import seedu.address.model.task.Task; @@ -36,8 +40,12 @@ public AddTaskCommand parse(String args) throws ParseException { } Description description = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_TASK_DESCRIPTION).get()); + // TODO: Implement deadline, isdone, description, tags + Deadline deadline = new Deadline(""); + Boolean isDone = false; + Set tags = new HashSet<>(); - Task task = new Task(description); + Task task = new Task(description, deadline, isDone, tags); return new AddTaskCommand(task); } diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java index 79244d71cf7..19e4efb616f 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/person/Name.java @@ -38,7 +38,6 @@ public static boolean isValidName(String test) { return test.matches(VALIDATION_REGEX); } - @Override public String toString() { return fullName; diff --git a/src/main/java/seedu/address/model/task/Deadline.java b/src/main/java/seedu/address/model/task/Deadline.java index d6db54fbb6a..80d80aa3473 100644 --- a/src/main/java/seedu/address/model/task/Deadline.java +++ b/src/main/java/seedu/address/model/task/Deadline.java @@ -3,26 +3,25 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + /** * Represents the deadline of a task in the TaskList. + * Deadline contains a date but not a time. */ public class Deadline { - // TODO: implement this class - // TODO: edit MESSAGE_CONSTRAINTS - public static final String MESSAGE_CONSTRAINTS = " "; - + public static final String MESSAGE_CONSTRAINTS = "Deadlines should be in the format DD-MM-YYYY"; - // TODO: Edit VALIDATION_REGEX (Copied from Description class for now) /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. + * Date should be in the format DD-MM-YYYY. + * Validation will be handled by DateTimeFormatter util class. */ - public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("dd MM yyyy"); - // TODO: Implement deadline (e.g. DateTime... ) - private final String deadline; + private final LocalDate date; - // TODO: Implement constructor /** * Constructs an {@code Deadline}. * @@ -30,21 +29,37 @@ public class Deadline { */ public Deadline(String deadline) { requireNonNull(deadline); - checkArgument(isValidDeadline(deadline), MESSAGE_CONSTRAINTS); - this.deadline = deadline; + date = LocalDate.parse(deadline); } - // TODO: Implement isValidDeadline method /** - * Returns true if a given string is a valid description. + * Returns if a given string is a valid email. */ public static boolean isValidDeadline(String test) { + // TODO: Try-catch used as control flow, refactor + try { + DATE_TIME_FORMATTER.parse(test); + } catch (DateTimeParseException e) { + return false; + } return true; } + @Override + public String toString() { + return date.format(DATE_TIME_FORMATTER); + } + @Override public boolean equals(Object other) { - return true; + return other == this // short circuit if same object + || (other instanceof Deadline // instanceof handles nulls + && date.equals(((Deadline) other).date)); // state check + } + + @Override + public int hashCode() { + return date.hashCode(); } } diff --git a/src/main/java/seedu/address/model/task/Description.java b/src/main/java/seedu/address/model/task/Description.java index 79fb5370a29..30fdc5ff767 100644 --- a/src/main/java/seedu/address/model/task/Description.java +++ b/src/main/java/seedu/address/model/task/Description.java @@ -11,7 +11,7 @@ public class Description { "Descriptions should only contain alphanumeric characters and spaces, and it should not be blank"; /* - * The first character of the address must not be a whitespace, + * The first character of the description must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; @@ -25,13 +25,12 @@ public class Description { */ public Description(String description) { requireNonNull(description); - checkArgument(isValidDescription(description), MESSAGE_CONSTRAINTS); taskDescription = description; } /** - * Returns true if a given string is a valid name. + * Returns true if a given string is a valid description. */ public static boolean isValidDescription(String test) { return test.matches(VALIDATION_REGEX); @@ -48,4 +47,9 @@ public boolean equals(Object other) { || (other instanceof Description // instanceof handles nulls && taskDescription.equals(((Description) other).taskDescription)); // state check } + + @Override + public int hashCode() { + return taskDescription.hashCode(); + } } diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java index 28f11957df4..b4a55d41d90 100644 --- a/src/main/java/seedu/address/model/task/Task.java +++ b/src/main/java/seedu/address/model/task/Task.java @@ -1,25 +1,37 @@ package seedu.address.model.task; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Objects; +import java.util.Set; + +import seedu.address.model.tag.Tag; + /** * Represents a Task in the TaskList. + * Guarantees: description is present and not null, field values are validated, immutable. */ public class Task { - private Description description; - private Deadline deadline; - private boolean isDone; + private final Description description; + private final Deadline deadline; + private Boolean isDone; + private final Set tags = new HashSet<>(); /** * A constructor that creates an instance of Task. * @param description The description of the task. */ - public Task(Description description) { - this.isDone = false; + public Task(Description description, Deadline deadline, Boolean isDone, Set tags) { + requireAllNonNull(description, deadline, isDone, tags); this.description = description; - // TODO: Edit after implementing Deadline class - this.deadline = new Deadline(""); + this.deadline = deadline; + this.isDone = isDone; + this.tags.addAll(tags); } /** @@ -42,10 +54,18 @@ public Deadline getDeadline() { * Returns true if task is done, false if task is not done. * @return boolean indicating task completion status. */ - public boolean getTaskStatus() { + public Boolean getStatus() { return isDone; } + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + /** * Marks task as done. */ @@ -72,4 +92,46 @@ public boolean containsKeywords(List descriptionKeywords, List tags = getTags(); + if (!tags.isEmpty()) { + builder.append("; Tags: "); + tags.forEach(builder::append); + } + return builder.toString(); + } } From fd95d47810efd26097a55327991e3bf0aac8d50e Mon Sep 17 00:00:00 2001 From: ryanguai Date: Wed, 12 Oct 2022 13:28:10 +0800 Subject: [PATCH 16/31] Add remove tag command for tasks --- .../logic/commands/tag/AddTagCommand.java | 17 +- .../logic/commands/tag/DeleteTagCommand.java | 162 ++++++++++++++++-- .../logic/parser/tag/AddTagCommandParser.java | 2 +- .../parser/tag/DeleteTagCommandParser.java | 29 ++-- 4 files changed, 169 insertions(+), 41 deletions(-) diff --git a/src/main/java/seedu/address/logic/commands/tag/AddTagCommand.java b/src/main/java/seedu/address/logic/commands/tag/AddTagCommand.java index c36a1d6b3ec..1fd386b48a2 100644 --- a/src/main/java/seedu/address/logic/commands/tag/AddTagCommand.java +++ b/src/main/java/seedu/address/logic/commands/tag/AddTagCommand.java @@ -57,10 +57,11 @@ public class AddTagCommand extends Command { * @param index of the person in the filtered person list to edit * @param editPersonDescriptor details to edit the person with */ - public AddTagCommand(Index index, EditPersonDescriptor editPersonDescriptor, boolean addTagToContact, - boolean addTagToTask, EditTaskDescriptor editTaskDescriptor) { + public AddTagCommand(Index index, EditPersonDescriptor editPersonDescriptor, EditTaskDescriptor editTaskDescriptor, + boolean addTagToContact, boolean addTagToTask) { requireNonNull(index); requireNonNull(editPersonDescriptor); + requireNonNull(editTaskDescriptor); this.index = index; this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); @@ -136,8 +137,8 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript } /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. + * Creates and returns a {@code Task} with the details of {@code taskToEdit} + * edited with {@code editTaskDescriptor}. */ private static Task createEditedTask(Task taskToEdit, EditTaskDescriptor editTaskDescriptor) { assert taskToEdit != null; @@ -345,15 +346,15 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { + if (!(other instanceof EditTaskDescriptor)) { return false; } // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; + EditTaskDescriptor e = (EditTaskDescriptor) other; - return getDescription().equals(e.getName()) - && getIsDone().equals(e.getPhone()) + return getDescription().equals(e.getDescription()) + && getIsDone().equals(e.getIsDone()) && getTags().equals(e.getTags()); } } diff --git a/src/main/java/seedu/address/logic/commands/tag/DeleteTagCommand.java b/src/main/java/seedu/address/logic/commands/tag/DeleteTagCommand.java index db0270e20f1..b77c6e85100 100644 --- a/src/main/java/seedu/address/logic/commands/tag/DeleteTagCommand.java +++ b/src/main/java/seedu/address/logic/commands/tag/DeleteTagCommand.java @@ -3,6 +3,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; import java.util.Collections; import java.util.HashSet; @@ -23,6 +24,8 @@ import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.task.Description; +import seedu.address.model.task.Task; /** * Edits the details of an existing person in the address book. @@ -42,42 +45,74 @@ public class DeleteTagCommand extends Command { public static final String MESSAGE_ADD_TAG_SUCCESS = "Deleted tag: %1$s"; public static final String MESSAGE_TAG_NOT_ADDED = "At least 1 tag to add must be provided."; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + public static final String MESSAGE_MISSING_INDEX = "At least 1 contact or task index must be provided."; private final Index index; private final EditPersonDescriptor editPersonDescriptor; + private final EditTaskDescriptor editTaskDescriptor; + private final boolean deleteTagFromContact; + private final boolean deleteTagFromTask; /** * @param index of the person in the filtered person list to edit * @param editPersonDescriptor details to edit the person with */ - public DeleteTagCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + public DeleteTagCommand(Index index, EditPersonDescriptor editPersonDescriptor, EditTaskDescriptor editTaskDescriptor, + boolean deleteTagFromContact, boolean deleteTagFromTask) { requireNonNull(index); requireNonNull(editPersonDescriptor); + requireNonNull(editTaskDescriptor); this.index = index; this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); + this.deleteTagFromContact = deleteTagFromContact; + this.deleteTagFromTask = deleteTagFromTask; + this.editTaskDescriptor = new EditTaskDescriptor(editTaskDescriptor); } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } + if (deleteTagFromContact) { + List lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + Person personToEdit = lastShownList.get(index.getZeroBased()); + Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.setPerson(personToEdit, editedPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_ADD_TAG_SUCCESS, + editPersonDescriptor.getTags().orElse(new HashSet<>()))); } + if (deleteTagFromTask) { + List lastShownTaskList = model.getFilteredTaskList(); - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_ADD_TAG_SUCCESS, - editPersonDescriptor.getTags().orElse(new HashSet<>()))); + if (index.getZeroBased() >= lastShownTaskList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Task taskToEdit = lastShownTaskList.get(index.getZeroBased()); + Task editedTask = createEditedTask(taskToEdit, editTaskDescriptor); + + if (!taskToEdit.isSameTask(editedTask) && model.hasTask(editedTask)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + model.setTask(taskToEdit, editedTask); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + return new CommandResult(String.format(MESSAGE_ADD_TAG_SUCCESS, + editTaskDescriptor.getTags().orElse(new HashSet<>()))); + } + throw new CommandException(MESSAGE_MISSING_INDEX); } /** @@ -101,6 +136,25 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); } + /** + * Creates and returns a {@code Task} with the details of {@code taskToEdit} + * edited with {@code editTaskDescriptor}. + */ + private static Task createEditedTask(Task taskToEdit, EditTaskDescriptor editTaskDescriptor) { + assert taskToEdit != null; + + Description updatedDescription = editTaskDescriptor.getDescription().orElse(taskToEdit.getDescription()); + boolean updatedIsDone = editTaskDescriptor.getIsDone().orElse(taskToEdit.getIsDone()); + Set newTags = editTaskDescriptor.getTags().orElse(new HashSet<>()); + Set updatedTags = new HashSet<>(); + updatedTags.addAll(taskToEdit.getTags()); + if (newTags.size() > 0) { + updatedTags.removeAll(newTags); + } + + return new Task(updatedDescription, updatedIsDone, updatedTags); + } + @Override public boolean equals(Object other) { // short circuit if same object @@ -222,5 +276,87 @@ && getAddress().equals(e.getAddress()) && getTags().equals(e.getTags()); } } + + /** + * Stores the details to edit the task with. Each non-empty field value will replace the + * corresponding field value of the task. + */ + public static class EditTaskDescriptor { + private Description description; + private boolean isDone; + private Set tags; + + public EditTaskDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditTaskDescriptor(EditTaskDescriptor toCopy) { + setDescription(toCopy.description); + setIsDone(toCopy.isDone); + setTags(toCopy.tags); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(description, isDone, tags); + } + + public void setDescription(Description description) { + this.description = description; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + public void setIsDone(boolean isDone) { + this.isDone = isDone; + } + + public Optional getIsDone() { + return Optional.ofNullable(isDone); + } + + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + + /** + * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code tags} is null. + */ + public Optional> getTags() { + return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddTagCommand.EditTaskDescriptor)) { + return false; + } + + // state check + AddTagCommand.EditTaskDescriptor e = (AddTagCommand.EditTaskDescriptor) other; + + return getDescription().equals(e.getDescription()) + && getIsDone().equals(e.getIsDone()) + && getTags().equals(e.getTags()); + } + } } diff --git a/src/main/java/seedu/address/logic/parser/tag/AddTagCommandParser.java b/src/main/java/seedu/address/logic/parser/tag/AddTagCommandParser.java index 719f3edc560..aac0b899098 100644 --- a/src/main/java/seedu/address/logic/parser/tag/AddTagCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/tag/AddTagCommandParser.java @@ -57,7 +57,7 @@ public AddTagCommand parse(String args) throws ParseException { throw new ParseException(AddTagCommand.MESSAGE_TAG_NOT_ADDED); } - return new AddTagCommand(index, editPersonDescriptor, addTagToContact, addTagToTask, editTaskDescriptor); + return new AddTagCommand(index, editPersonDescriptor, editTaskDescriptor, addTagToContact, addTagToTask); } /** diff --git a/src/main/java/seedu/address/logic/parser/tag/DeleteTagCommandParser.java b/src/main/java/seedu/address/logic/parser/tag/DeleteTagCommandParser.java index 5c0980077c1..c241bd96fbd 100644 --- a/src/main/java/seedu/address/logic/parser/tag/DeleteTagCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/tag/DeleteTagCommandParser.java @@ -2,11 +2,8 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.*; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK; import java.util.Collection; import java.util.Collections; @@ -37,10 +34,13 @@ public class DeleteTagCommandParser implements Parser { public DeleteTagCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_CONTACT, PREFIX_TASK, PREFIX_TAG); Index index; + boolean deleteTagFromContact = argMultimap.getValue(PREFIX_CONTACT).isPresent(); + boolean deleteTagFromTask = argMultimap.getValue(PREFIX_TASK).isPresent(); + try { index = ParserUtil.parseIndex(argMultimap.getPreamble()); } catch (ParseException pe) { @@ -48,25 +48,16 @@ public DeleteTagCommand parse(String args) throws ParseException { } EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); - } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); - } parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + DeleteTagCommand.EditTaskDescriptor editTaskDescriptor = new DeleteTagCommand.EditTaskDescriptor(); + parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editTaskDescriptor::setTags); + if (!editPersonDescriptor.isAnyFieldEdited()) { throw new ParseException(AddTagCommand.MESSAGE_TAG_NOT_ADDED); } - return new DeleteTagCommand(index, editPersonDescriptor); + return new DeleteTagCommand(index, editPersonDescriptor, editTaskDescriptor, deleteTagFromContact, deleteTagFromTask); } /** From 716bdf2b120492abd2db2a30987d49061af80bf4 Mon Sep 17 00:00:00 2001 From: ryanguai Date: Wed, 12 Oct 2022 13:34:01 +0800 Subject: [PATCH 17/31] Correct checkstyle errors --- .../seedu/address/logic/commands/tag/DeleteTagCommand.java | 4 ++-- .../address/logic/parser/tag/DeleteTagCommandParser.java | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/seedu/address/logic/commands/tag/DeleteTagCommand.java b/src/main/java/seedu/address/logic/commands/tag/DeleteTagCommand.java index b77c6e85100..acc9d564762 100644 --- a/src/main/java/seedu/address/logic/commands/tag/DeleteTagCommand.java +++ b/src/main/java/seedu/address/logic/commands/tag/DeleteTagCommand.java @@ -57,8 +57,8 @@ public class DeleteTagCommand extends Command { * @param index of the person in the filtered person list to edit * @param editPersonDescriptor details to edit the person with */ - public DeleteTagCommand(Index index, EditPersonDescriptor editPersonDescriptor, EditTaskDescriptor editTaskDescriptor, - boolean deleteTagFromContact, boolean deleteTagFromTask) { + public DeleteTagCommand(Index index, EditPersonDescriptor editPersonDescriptor, + EditTaskDescriptor editTaskDescriptor, boolean deleteTagFromContact, boolean deleteTagFromTask) { requireNonNull(index); requireNonNull(editPersonDescriptor); requireNonNull(editTaskDescriptor); diff --git a/src/main/java/seedu/address/logic/parser/tag/DeleteTagCommandParser.java b/src/main/java/seedu/address/logic/parser/tag/DeleteTagCommandParser.java index c241bd96fbd..782d531b4c1 100644 --- a/src/main/java/seedu/address/logic/parser/tag/DeleteTagCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/tag/DeleteTagCommandParser.java @@ -2,7 +2,8 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.*; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONTACT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK; import java.util.Collection; @@ -57,7 +58,8 @@ public DeleteTagCommand parse(String args) throws ParseException { throw new ParseException(AddTagCommand.MESSAGE_TAG_NOT_ADDED); } - return new DeleteTagCommand(index, editPersonDescriptor, editTaskDescriptor, deleteTagFromContact, deleteTagFromTask); + return new DeleteTagCommand(index, editPersonDescriptor, editTaskDescriptor, + deleteTagFromContact, deleteTagFromTask); } /** From 985b1e4d785d146742c09f98014a94e4820f7440 Mon Sep 17 00:00:00 2001 From: junwei-tan Date: Wed, 12 Oct 2022 17:16:20 +0800 Subject: [PATCH 18/31] Update Task, Description and TaskContainsKeywordsPredicate class --- .../seedu/address/model/task/Description.java | 9 ++++++++ .../java/seedu/address/model/task/Task.java | 23 +++++++++++++++---- .../task/TaskContainsKeywordsPredicate.java | 2 +- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/main/java/seedu/address/model/task/Description.java b/src/main/java/seedu/address/model/task/Description.java index 30fdc5ff767..612b43e76b0 100644 --- a/src/main/java/seedu/address/model/task/Description.java +++ b/src/main/java/seedu/address/model/task/Description.java @@ -36,6 +36,15 @@ public static boolean isValidDescription(String test) { return test.matches(VALIDATION_REGEX); } + /** + * Returns true if a given description is the same as this description. (case-insensitive) + * + * @param otherDescription Description to compare with. + */ + public boolean equalsIgnoreCase(Description otherDescription) { + return taskDescription.equalsIgnoreCase(otherDescription.taskDescription); + } + @Override public String toString() { return taskDescription; diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java index b4a55d41d90..394752b383d 100644 --- a/src/main/java/seedu/address/model/task/Task.java +++ b/src/main/java/seedu/address/model/task/Task.java @@ -10,7 +10,6 @@ import seedu.address.model.tag.Tag; - /** * Represents a Task in the TaskList. * Guarantees: description is present and not null, field values are validated, immutable. @@ -88,14 +87,28 @@ public void unmarkTask() { * @param deadlineKeywords Possibly empty list containing keywords for {@code Deadline}. * @return boolean indicating if task contains supplied keywords. */ - public boolean containsKeywords(List descriptionKeywords, List deadlineKeywords) { - return (descriptionKeywords.isEmpty() || descriptionKeywords.contains(description)) + public boolean containsKeywordsCaseInsensitive(List descriptionKeywords, + List deadlineKeywords) { + return (descriptionKeywords.isEmpty() || descriptionKeywords.stream().anyMatch(description::equalsIgnoreCase)) && (deadlineKeywords.isEmpty() || deadlineKeywords.contains(deadline)); } /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. + * Returns true if both tasks have the same name. + * This defines a weaker notion of equality between two tasks. + */ + public boolean isSameTask(Task otherTask) { + if (otherTask == this) { + return true; + } + + return otherTask != null + && description.equalsIgnoreCase(otherTask.getDescription()); + } + + /** + * Returns true if both tasks have the same fields. + * This defines a stronger notion of equality between two tasks. */ @Override public boolean equals(Object other) { diff --git a/src/main/java/seedu/address/model/task/TaskContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/task/TaskContainsKeywordsPredicate.java index 55a4fac911a..7247a06fcb9 100644 --- a/src/main/java/seedu/address/model/task/TaskContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/task/TaskContainsKeywordsPredicate.java @@ -32,7 +32,7 @@ public TaskContainsKeywordsPredicate(List descriptionKeywords, */ @Override public boolean test(Task task) { - return task.containsKeywords(descriptionKeywords, deadlineKeywords); + return task.containsKeywordsCaseInsensitive(descriptionKeywords, deadlineKeywords); } @Override From 50cef7f97801c3794bcb6ad8caa115372e0c4fd6 Mon Sep 17 00:00:00 2001 From: junwei-tan Date: Wed, 12 Oct 2022 23:32:24 +0800 Subject: [PATCH 19/31] Update javadoc --- src/main/java/seedu/address/model/task/Deadline.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/seedu/address/model/task/Deadline.java b/src/main/java/seedu/address/model/task/Deadline.java index 80d80aa3473..f3cea2e6736 100644 --- a/src/main/java/seedu/address/model/task/Deadline.java +++ b/src/main/java/seedu/address/model/task/Deadline.java @@ -34,7 +34,7 @@ public Deadline(String deadline) { } /** - * Returns if a given string is a valid email. + * Returns if a given string is a valid deadline. */ public static boolean isValidDeadline(String test) { // TODO: Try-catch used as control flow, refactor From dfb56f57f1534211d0b2e5058dc8c1822e51d9c2 Mon Sep 17 00:00:00 2001 From: ryanguai Date: Wed, 12 Oct 2022 23:45:44 +0800 Subject: [PATCH 20/31] Correct typos in docs --- src/main/java/seedu/address/model/task/Task.java | 1 + src/main/java/seedu/address/model/task/TaskList.java | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java index e74a129b134..830ca0d3c4b 100644 --- a/src/main/java/seedu/address/model/task/Task.java +++ b/src/main/java/seedu/address/model/task/Task.java @@ -96,6 +96,7 @@ public boolean isSameTask(Task otherTask) { && otherTask.getDescription().equals(getDescription()); } + /** * Returns true if task contains any of the description or deadline keywords. * By default, empty lists will return true. * diff --git a/src/main/java/seedu/address/model/task/TaskList.java b/src/main/java/seedu/address/model/task/TaskList.java index dc002ffa04c..14f3bd8d90e 100644 --- a/src/main/java/seedu/address/model/task/TaskList.java +++ b/src/main/java/seedu/address/model/task/TaskList.java @@ -44,9 +44,9 @@ public Iterator iterator() { } /** - * Replaces the person {@code target} in the list with {@code editedTask}. + * Replaces the task {@code target} in the list with {@code editedTask}. * {@code target} must exist in the list. - * The person identity of {@code editedTask} must not be the same as another existing person in the list. + * The task identity of {@code editedTask} must not be the same as another existing task in the list. */ public void setTask(Task target, Task editedTask) { requireAllNonNull(target, editedTask); @@ -64,7 +64,7 @@ public void setTask(Task target, Task editedTask) { } /** - * Returns true if the list contains an equivalent person as the given argument. + * Returns true if the list contains an equivalent task as the given argument. */ public boolean contains(Task toCheck) { requireNonNull(toCheck); From 2315eac276c1a327a0193efac4579ff160143fa9 Mon Sep 17 00:00:00 2001 From: ryanguai Date: Thu, 13 Oct 2022 00:09:19 +0800 Subject: [PATCH 21/31] Fix wrong import order in Task --- src/main/java/seedu/address/model/task/Task.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java index 830ca0d3c4b..c78ca081fc1 100644 --- a/src/main/java/seedu/address/model/task/Task.java +++ b/src/main/java/seedu/address/model/task/Task.java @@ -4,8 +4,8 @@ import java.util.Collections; import java.util.HashSet; -import java.util.Set; import java.util.List; +import java.util.Set; import seedu.address.model.tag.Tag; From 495fed341eaeee15f3b7f1bc9e6758b5122726ee Mon Sep 17 00:00:00 2001 From: alvintfl Date: Thu, 13 Oct 2022 01:11:18 +0800 Subject: [PATCH 22/31] Allow add task command to be parsed --- .../java/seedu/address/logic/parser/AddressBookParser.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 4eba5241121..13ca00adfe5 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -17,6 +17,7 @@ import seedu.address.logic.commands.contact.ListContactCommand; import seedu.address.logic.commands.tag.AddTagCommand; import seedu.address.logic.commands.tag.DeleteTagCommand; +import seedu.address.logic.commands.task.AddTaskCommand; import seedu.address.logic.commands.task.FindTaskCommand; import seedu.address.logic.commands.task.ListTaskCommand; import seedu.address.logic.commands.task.MarkTaskCommand; @@ -28,6 +29,7 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.logic.parser.tag.AddTagCommandParser; import seedu.address.logic.parser.tag.DeleteTagCommandParser; +import seedu.address.logic.parser.task.AddTaskCommandParser; import seedu.address.logic.parser.task.FindTaskCommandParser; import seedu.address.logic.parser.task.MarkTaskCommandParser; import seedu.address.logic.parser.task.UnmarkTaskCommandParser; @@ -92,6 +94,9 @@ public Command parseCommand(String userInput) throws ParseException { case DeleteTagCommand.COMMAND_WORD: return new DeleteTagCommandParser().parse(arguments); + case AddTaskCommand.COMMAND_WORD: + return new AddTaskCommandParser().parse(arguments); + case FindTaskCommand.COMMAND_WORD: return new FindTaskCommandParser().parse(arguments); From e90734c2a2f12f8d209f6e22ffbf20e124d7315b Mon Sep 17 00:00:00 2001 From: alvintfl Date: Thu, 13 Oct 2022 01:12:23 +0800 Subject: [PATCH 23/31] Add deadline to be displayed by GUI Users have no way to see the deadline. Let's modfiy the GUI to be able to display the deadline. --- .../address/logic/parser/task/AddTaskCommandParser.java | 6 ++++-- src/main/java/seedu/address/model/task/Deadline.java | 3 ++- src/main/java/seedu/address/model/task/Task.java | 4 ++++ src/main/java/seedu/address/ui/TaskCard.java | 3 +++ src/main/resources/view/TaskListCard.fxml | 1 + 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/seedu/address/logic/parser/task/AddTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/task/AddTaskCommandParser.java index 607e513356e..11e3440dcca 100644 --- a/src/main/java/seedu/address/logic/parser/task/AddTaskCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/task/AddTaskCommandParser.java @@ -4,6 +4,8 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DEADLINE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DESCRIPTION; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.HashSet; import java.util.Set; import java.util.stream.Stream; @@ -40,8 +42,8 @@ public AddTaskCommand parse(String args) throws ParseException { } Description description = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_TASK_DESCRIPTION).get()); - // TODO: Implement deadline, isdone, description, tags - Deadline deadline = new Deadline(""); + // TODO: fix deadline + Deadline deadline = new Deadline("2020-11-11"); Boolean isDone = false; Set tags = new HashSet<>(); diff --git a/src/main/java/seedu/address/model/task/Deadline.java b/src/main/java/seedu/address/model/task/Deadline.java index f3cea2e6736..f39c13b70b6 100644 --- a/src/main/java/seedu/address/model/task/Deadline.java +++ b/src/main/java/seedu/address/model/task/Deadline.java @@ -29,7 +29,8 @@ public class Deadline { */ public Deadline(String deadline) { requireNonNull(deadline); - checkArgument(isValidDeadline(deadline), MESSAGE_CONSTRAINTS); + //TODO Fix isValidDeadline + //checkArgument(isValidDeadline(deadline), MESSAGE_CONSTRAINTS); date = LocalDate.parse(deadline); } diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java index 0c7b8c888f2..64e3d22d88b 100644 --- a/src/main/java/seedu/address/model/task/Task.java +++ b/src/main/java/seedu/address/model/task/Task.java @@ -60,6 +60,10 @@ public Boolean getStatus() { return isDone; } + /** + * Returns an icon representing if the task is done. + * @return String representation of an icon. + */ public String getStatusIcon() { return (isDone ? "[X]" : "[ ]"); } diff --git a/src/main/java/seedu/address/ui/TaskCard.java b/src/main/java/seedu/address/ui/TaskCard.java index 98772d54e2f..a5c73bd04b1 100644 --- a/src/main/java/seedu/address/ui/TaskCard.java +++ b/src/main/java/seedu/address/ui/TaskCard.java @@ -36,6 +36,8 @@ public class TaskCard extends UiPart { @FXML private Label statusIcon; @FXML + private Label deadline; + @FXML private FlowPane tags; /** @@ -47,6 +49,7 @@ public TaskCard(Task task, int displayedIndex) { id.setText(displayedIndex + ". "); description.setText(task.getDescription().toString()); statusIcon.setText(task.getStatusIcon()); + deadline.setText(task.getDeadline().toString()); task.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); diff --git a/src/main/resources/view/TaskListCard.fxml b/src/main/resources/view/TaskListCard.fxml index 210499ee352..1ca79c63b20 100644 --- a/src/main/resources/view/TaskListCard.fxml +++ b/src/main/resources/view/TaskListCard.fxml @@ -29,6 +29,7 @@