diff --git a/.gitignore b/.gitignore index 823d175eb670..a416207a776b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ lib/* *.csv config.json src/test/data/sandbox/ +src/test/java/systemtests/AddCommandSystemTest preferences.json .DS_Store ./screenshot*.png diff --git a/Collate-TUI.jar b/Collate-TUI.jar new file mode 100644 index 000000000000..2b9e032106dd Binary files /dev/null and b/Collate-TUI.jar differ diff --git a/README.adoc b/README.adoc index 03eff3a4d191..96ae0a016663 100644 --- a/README.adoc +++ b/README.adoc @@ -1,27 +1,33 @@ -= Address Book (Level 4) += CollegeZone ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] +https://travis-ci.org/CS2103JAN2018-T09-B2/main[image:https://travis-ci.org/CS2103JAN2018-T09-B2/main.svg?branch=master[Build Status]] https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] +https://coveralls.io/github/CS2103JAN2018-T09-B2/main?branch=master[image:https://coveralls.io/repos/github/CS2103JAN2018-T09-B2/main/badge.svg?branch=master[Coverage Status]] https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] ifdef::env-github[] -image::docs/images/Ui.png[width="600"] +image::docs/images/Ui.png[width="800"] endif::[] ifndef::env-github[] -image::images/Ui.png[width="600"] +image::images/Ui.png[width="800"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +Welcome to CollegeZone! CollegeZone is a custom application made for *National University of Singapore (NUS) Residential College 4 (RC4) students*. + +CollegeZone is designed for a RC4 student to manage their contacts with other RC4 students, manage their goals and to manage their time. +You can find many interesting features available in CollegeZone such as: + + +* recording meetups + +* rating friends + +* creating goals + +* creating reminders + +* and many more! + +The main reasons NUS freshmen chooses to stay in RC4 is to *forge new friendships* and to *perform well in their studies*. CollegeZone aims to aid in RC4 residents tasks management and maintaining the friendships forged in RC4. + +With CollegeZone's unique features, we aim to benefit your everyday life. + +Head over to our link:docs/UserGuide.adoc[user guide] to get started with CollegeZone! == Site Map @@ -36,5 +42,7 @@ endif::[] * Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by _Marco Jakob_. * Libraries used: https://github.com/TomasMikula/EasyBind[EasyBind], https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit4[JUnit4] +* Credits to http://natty.joestelmach.com/[Joestalmach] for the date parsing NLP library +* Credits to to http://dlsc.com/products/calendarfx/[Dirk Lemmermann] for his calendar software == Licence : link:LICENSE[MIT] diff --git a/build.gradle b/build.gradle index 50cd2ae52efc..39fdbf4fa11b 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,8 @@ dependencies { compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' compile group: 'com.google.guava', name: 'guava', version: '19.0' + compile group: 'com.joestelmach', name: 'natty', version: '0.12' + compile group: 'com.calendarfx', name: 'view', version: '8.4.2' testCompile group: 'junit', name: 'junit', version: '4.12' testCompile group: 'org.testfx', name: 'testfx-core', version: testFxVersion diff --git a/collated/functional/deborahlow97.md b/collated/functional/deborahlow97.md new file mode 100644 index 000000000000..de79eba0e3b6 --- /dev/null +++ b/collated/functional/deborahlow97.md @@ -0,0 +1,4160 @@ +# deborahlow97 +###### \java\seedu\address\commons\events\ui\ThemeSwitchRequestEvent.java +``` java +/** + * Indicates that a theme switch is requested. + */ +public class ThemeSwitchRequestEvent extends BaseEvent { + public final String themeToChangeTo; + + public ThemeSwitchRequestEvent(String themeToChangeTo) { + this.themeToChangeTo = themeToChangeTo; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} +``` +###### \java\seedu\address\logic\commands\AddGoalCommand.java +``` java +/** + * Adds a goal to CollegeZone. + */ +public class AddGoalCommand extends UndoableCommand { + public static final String COMMAND_WORD = "+goal"; + public static final String COMMAND_ALIAS_1 = "+g"; + public static final String COMMAND_ALIAS_2 = "addgoal"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " " + + PREFIX_IMPORTANCE + " " + + PREFIX_GOAL_TEXT + " "; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a goal to Goals Page. \n" + + "Parameters: " + + PREFIX_IMPORTANCE + "IMPORTANCE " + + PREFIX_GOAL_TEXT + "TEXT \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_IMPORTANCE + "3 " + + PREFIX_GOAL_TEXT + "lose weight \n"; + + public static final String MESSAGE_SUCCESS = "New goal added: %1$s"; + public static final String MESSAGE_DUPLICATE_GOAL = "This goal already exists in the Goals Page"; + + private final Goal toAdd; + + /** + * Creates an AddGoalCommand to add the specified {@code Goal} + */ + public AddGoalCommand(Goal goal) { + requireNonNull(goal); + toAdd = goal; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + model.addGoal(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicateGoalException e) { + throw new CommandException(MESSAGE_DUPLICATE_GOAL); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddGoalCommand // instanceof handles nulls + && toAdd.equals(((AddGoalCommand) other).toAdd)); + } +} +``` +###### \java\seedu\address\logic\commands\CompleteGoalCommand.java +``` java +/** + * Edits the details of an existing goal in CollegeZone. + */ +public class CompleteGoalCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "!goal"; + public static final String COMMAND_ALIAS_1 = "!g"; + public static final String COMMAND_ALIAS_2 = "completegoal"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Indicate completion of the goal identified " + + "by the index number used in the last goal listing.\n " + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 "; + + public static final String MESSAGE_COMPLETE_GOAL_SUCCESS = "Completed Goal! : %1$s"; + + private final Index index; + private final CompleteGoalDescriptor completeGoalDescriptor; + + private Goal goalToUpdate; + private Goal updatedGoal; + + /** + * @param index of the goal in the filtered goal list to update + */ + public CompleteGoalCommand(Index index, CompleteGoalDescriptor completeGoalDescriptor) { + requireNonNull(index); + requireNonNull(completeGoalDescriptor); + + this.index = index; + this.completeGoalDescriptor = new CompleteGoalDescriptor(completeGoalDescriptor); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.updateGoalWithoutParameters(goalToUpdate, updatedGoal); + } catch (GoalNotFoundException pnfe) { + throw new AssertionError("The target goal cannot be missing"); + } + model.updateFilteredGoalList(PREDICATE_SHOW_ALL_GOALS); + return new CommandResult(String.format(MESSAGE_COMPLETE_GOAL_SUCCESS, updatedGoal)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredGoalList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + } + + goalToUpdate = lastShownList.get(index.getZeroBased()); + if (goalToUpdate.getCompletion().hasCompleted) { + throw new CommandException(Messages.MESSAGE_GOAL_COMPLETED_ERROR); + } + updatedGoal = createUpdatedGoal(goalToUpdate, completeGoalDescriptor); + } + + /** + * Creates and returns a {@code Goal} with the details of {@code goalToUpdate} + * edited with {@code completeGoalDescriptor}. + */ + private static Goal createUpdatedGoal(Goal goalToUpdate, CompleteGoalDescriptor completeGoalDescriptor) { + assert goalToUpdate != null; + + GoalText goalText = goalToUpdate.getGoalText(); + Importance importance = goalToUpdate.getImportance(); + StartDateTime startDateTime = goalToUpdate.getStartDateTime(); + EndDateTime updatedEndDateTime = completeGoalDescriptor.getEndDateTime() + .orElse(goalToUpdate.getEndDateTime()); + Completion updatedCompletion = completeGoalDescriptor.getCompletion().orElse(goalToUpdate.getCompletion()); + + return new Goal(importance, goalText, startDateTime, updatedEndDateTime, updatedCompletion); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof CompleteGoalCommand)) { + return false; + } + + // state check + CompleteGoalCommand e = (CompleteGoalCommand) other; + return index.equals(e.index) + && completeGoalDescriptor.equals(e.completeGoalDescriptor) + && Objects.equals(goalToUpdate, e.goalToUpdate); + } + + /** + * Stores the details to update the goal with. + */ + public static class CompleteGoalDescriptor { + + private EndDateTime endDateTime; + private Completion completion; + + public CompleteGoalDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code toCopy} is used internally. + */ + public CompleteGoalDescriptor(CompleteGoalDescriptor toCopy) { + setEndDateTime(toCopy.endDateTime); + setCompletion(toCopy.completion); + } + + public void setEndDateTime(EndDateTime endDateTime) { + this.endDateTime = endDateTime; + } + + public Optional getEndDateTime() { + return Optional.ofNullable(endDateTime); + } + + public void setCompletion(Completion completion) { + this.completion = completion; + } + + public Optional getCompletion() { + return Optional.ofNullable(completion); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof CompleteGoalDescriptor)) { + return false; + } + + // state check + CompleteGoalDescriptor e = (CompleteGoalDescriptor) other; + + return getEndDateTime().equals(e.getEndDateTime()) + && getCompletion().equals(e.getCompletion()); + } + } +} +``` +###### \java\seedu\address\logic\commands\DeleteGoalCommand.java +``` java +/** + * Deletes a goal identified using it's last displayed index from CollegeZone. + */ +public class DeleteGoalCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "-goal"; + + public static final String COMMAND_ALIAS_1 = "-g"; + + public static final String COMMAND_ALIAS_2 = "deletegoal"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the goal identified by the index number used in the goal listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_GOAL_SUCCESS = "Deleted Goal: %1$s"; + + private final Index targetIndex; + + private Goal goalToDelete; + + public DeleteGoalCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(goalToDelete); + try { + model.deleteGoal(goalToDelete); + } catch (GoalNotFoundException pnfe) { + throw new AssertionError("The target goal cannot be missing"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_GOAL_SUCCESS, goalToDelete)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredGoalList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + } + + goalToDelete = lastShownList.get(targetIndex.getZeroBased()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteGoalCommand // instanceof handles nulls + && this.targetIndex.equals(((DeleteGoalCommand) other).targetIndex) // state check + && Objects.equals(this.goalToDelete, ((DeleteGoalCommand) other).goalToDelete)); + } +} +``` +###### \java\seedu\address\logic\commands\EditGoalCommand.java +``` java +/** + * Edits the details of an existing goal in CollegeZone. + */ +public class EditGoalCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "~goal"; + public static final String COMMAND_ALIAS_1 = "~g"; + public static final String COMMAND_ALIAS_2 = "editgoal"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the goal identified " + + "by the index number used in the last goal listing. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_GOAL_TEXT + "GOAL TEXT] " + + "[" + PREFIX_IMPORTANCE + "IMPORTANCE] \n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_IMPORTANCE + "2 "; + + public static final String MESSAGE_EDIT_GOAL_SUCCESS = "Edited Goal: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_GOAL = "This goal already exists in the address book."; + + private final Index index; + private final EditGoalDescriptor editGoalDescriptor; + + private Goal goalToEdit; + private Goal editedGoal; + + /** + * @param index of the goal in the filtered goal list to edit + * @param editGoalDescriptor details to edit the goal with + */ + public EditGoalCommand(Index index, EditGoalDescriptor editGoalDescriptor) { + requireNonNull(index); + requireNonNull(editGoalDescriptor); + + this.index = index; + this.editGoalDescriptor = new EditGoalDescriptor(editGoalDescriptor); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.updateGoal(goalToEdit, editedGoal); + } catch (DuplicateGoalException dpe) { + throw new CommandException(MESSAGE_DUPLICATE_GOAL); + } catch (GoalNotFoundException pnfe) { + throw new AssertionError("The target goal cannot be missing"); + } + model.updateFilteredGoalList(PREDICATE_SHOW_ALL_GOALS); + return new CommandResult(String.format(MESSAGE_EDIT_GOAL_SUCCESS, editedGoal)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredGoalList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + } + + goalToEdit = lastShownList.get(index.getZeroBased()); + editedGoal = createEditedGoal(goalToEdit, editGoalDescriptor); + } + + /** + * Creates and returns a {@code Goal} with the details of {@code goalToEdit} + * edited with {@code editGoalDescriptor}. + */ + private static Goal createEditedGoal(Goal goalToEdit, EditGoalDescriptor editGoalDescriptor) { + assert goalToEdit != null; + + GoalText updatedGoalText = editGoalDescriptor.getGoalText().orElse(goalToEdit.getGoalText()); + Importance updatedImportance = editGoalDescriptor.getImportance().orElse(goalToEdit.getImportance()); + StartDateTime startDateTime = goalToEdit.getStartDateTime(); + EndDateTime endDateTime = goalToEdit.getEndDateTime(); + Completion completion = goalToEdit.getCompletion(); + return new Goal(updatedImportance, updatedGoalText, startDateTime, endDateTime, completion); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditGoalCommand)) { + return false; + } + + // state check + EditGoalCommand e = (EditGoalCommand) other; + return index.equals(e.index) + && editGoalDescriptor.equals(e.editGoalDescriptor) + && Objects.equals(goalToEdit, e.goalToEdit); + } + + /** + * Stores the details to edit the goal with. Each non-empty field value will replace the + * corresponding field value of the goal. + */ + public static class EditGoalDescriptor { + private GoalText goalText; + private Importance importance; + + public EditGoalDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditGoalDescriptor(EditGoalDescriptor toCopy) { + setGoalText(toCopy.goalText); + setImportance(toCopy.importance); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(this.goalText, this.importance); + } + + public void setGoalText(GoalText goalText) { + this.goalText = goalText; + } + + public Optional getGoalText() { + return Optional.ofNullable(goalText); + } + + public void setImportance(Importance importance) { + this.importance = importance; + } + + public Optional getImportance() { + return Optional.ofNullable(importance); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditGoalDescriptor)) { + return false; + } + + // state check + EditGoalDescriptor e = (EditGoalDescriptor) other; + + return getGoalText().equals(e.getGoalText()) + && getImportance().equals(e.getImportance()); + } + } +} + +``` +###### \java\seedu\address\logic\commands\OngoingGoalCommand.java +``` java +/** + * Edits the details of an existing goal in CollegeZone. + */ +public class OngoingGoalCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "-!goal"; + public static final String COMMAND_ALIAS_1 = "-!g"; + public static final String COMMAND_ALIAS_2 = "ongoinggoal"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Indicate identified goal is not completed " + + "and still ongoing.\n" + + "Goal is identified " + + "by the index number used in the last goal listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 "; + + public static final String MESSAGE_ONGOING_GOAL_SUCCESS = "Ongoing Goal! : %1$s"; + + private final Index index; + private final OngoingGoalDescriptor ongoingGoalDescriptor; + + private Goal goalToUpdate; + private Goal updatedGoal; + + /** + * @param index of the goal in the filtered goal list to update + */ + public OngoingGoalCommand(Index index, OngoingGoalDescriptor ongoingGoalDescriptor) { + requireNonNull(index); + requireNonNull(ongoingGoalDescriptor); + + this.index = index; + this.ongoingGoalDescriptor = new OngoingGoalDescriptor(ongoingGoalDescriptor); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.updateGoalWithoutParameters(goalToUpdate, updatedGoal); + } catch (GoalNotFoundException pnfe) { + throw new AssertionError("The target goal cannot be missing"); + } + model.updateFilteredGoalList(PREDICATE_SHOW_ALL_GOALS); + return new CommandResult(String.format(MESSAGE_ONGOING_GOAL_SUCCESS, updatedGoal)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredGoalList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + } + + goalToUpdate = lastShownList.get(index.getZeroBased()); + if (!goalToUpdate.getCompletion().hasCompleted) { + throw new CommandException(Messages.MESSAGE_GOAL_ONGOING_ERROR); + } + updatedGoal = createUpdatedGoal(goalToUpdate, ongoingGoalDescriptor); + } + + /** + * Creates and returns a {@code Goal} with the details of {@code goalToUpdate} + * edited with {@code ongoingGoalDescriptor}. + */ + private static Goal createUpdatedGoal(Goal goalToUpdate, OngoingGoalDescriptor ongoingGoalDescriptor) { + assert goalToUpdate != null; + + GoalText goalText = ongoingGoalDescriptor.getGoalText().orElse(goalToUpdate.getGoalText()); + Importance importance = ongoingGoalDescriptor.getImportance().orElse(goalToUpdate.getImportance()); + StartDateTime startDateTime = ongoingGoalDescriptor.getStartDateTime().orElse(goalToUpdate.getStartDateTime()); + EndDateTime updatedEndDateTime = ongoingGoalDescriptor.getEndDateTime() + .orElse(goalToUpdate.getEndDateTime()); + Completion updatedCompletion = ongoingGoalDescriptor.getCompletion().orElse(goalToUpdate.getCompletion()); + + return new Goal(importance, goalText, startDateTime, updatedEndDateTime, updatedCompletion); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof OngoingGoalCommand)) { + return false; + } + + // state check + OngoingGoalCommand e = (OngoingGoalCommand) other; + return index.equals(e.index) + && ongoingGoalDescriptor.equals(e.ongoingGoalDescriptor) + && Objects.equals(goalToUpdate, e.goalToUpdate); + } + + /** + * Stores the details to update the goal with. + */ + public static class OngoingGoalDescriptor { + private GoalText goalText; + private Importance importance; + private StartDateTime startDateTime; + private EndDateTime endDateTime; + private Completion completion; + + public OngoingGoalDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code toCopy} is used internally. + */ + public OngoingGoalDescriptor(OngoingGoalDescriptor toCopy) { + setEndDateTime(toCopy.endDateTime); + setCompletion(toCopy.completion); + } + + public void setEndDateTime(EndDateTime endDateTime) { + this.endDateTime = endDateTime; + } + + public Optional getEndDateTime() { + return Optional.ofNullable(endDateTime); + } + + public void setCompletion(Completion completion) { + this.completion = completion; + } + + public Optional getCompletion() { + return Optional.ofNullable(completion); + } + + public Optional getStartDateTime() { + return Optional.ofNullable(startDateTime); + } + + public Optional getImportance() { + return Optional.ofNullable(importance); + } + public Optional getGoalText() { + return Optional.ofNullable(goalText); + } + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof OngoingGoalDescriptor)) { + return false; + } + + // state check + OngoingGoalDescriptor e = (OngoingGoalDescriptor) other; + + return getGoalText().equals(e.getGoalText()) + && getImportance().equals(e.getImportance()) + && getStartDateTime().equals(e.getStartDateTime()) + && getEndDateTime().equals(e.getEndDateTime()) + && getCompletion().equals(e.getCompletion()); + } + } +} +``` +###### \java\seedu\address\logic\commands\SortGoalCommand.java +``` java +/** + * Sorts goal list in CollegeZone based on sort field entered by user. + */ +public class SortGoalCommand extends Command { + + public static final String COMMAND_WORD = "sortgoal"; + public static final String COMMAND_ALIAS = "sgoal"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sorts CollegeZone's goals based on the field entered.\n" + + "Parameters: " + + PREFIX_SORT_FIELD + "FIELD (must be 'importance', 'startdatetime' or 'completion') " + + PREFIX_SORT_ORDER + "ORDER (must be either 'ascending' or 'descending')\n" + + "Example: " + COMMAND_WORD + " f/completion o/ascending"; + + public static final String MESSAGE_SUCCESS = "Sorted all goals by %s and %s"; + private String sortField; + private String sortOrder; + + public SortGoalCommand(String field, String order) { + this.sortField = field; + this.sortOrder = order; + } + + @Override + public CommandResult execute() throws CommandException { + try { + model.sortGoal(sortField, sortOrder); + } catch (EmptyGoalListException egle) { + throw new CommandException(Messages.MESSAGE_INVALID_SORT_COMMAND_USAGE); + } + model.updateFilteredGoalList(PREDICATE_SHOW_ALL_GOALS); + return new CommandResult(String.format(MESSAGE_SUCCESS, sortField, sortOrder)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortGoalCommand // instanceof handles nulls + && sortField.equals(((SortGoalCommand) other).sortField)); + } +} +``` +###### \java\seedu\address\logic\commands\ThemeCommand.java +``` java +/** + * Changes the CollegeZone colour theme to either dark, bubblegum or light. + */ +public class ThemeCommand extends Command { + public static final String COMMAND_WORD = "theme"; + public static final String COMMAND_ALIAS = "th"; + public static final String MESSAGE_SUCCESS = "Theme successfully changed!"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Changes the theme to the theme word entered.\n" + + "Parameters: COLOUR THEME\n" + + "(Colour theme words: dark, bubblegum, light)\n" + + "Example: " + COMMAND_WORD + " dark\n"; + public static final String MESSAGE_INVALID_THEME_COLOUR = "Theme colour entered is invalid.\n" + + "Possible theme colours:\n" + + "(Colour theme words: dark, bubblegum, light)\n"; + private final String themeColour; + + /** + * Creates a ThemeCommand based on the specified themeColour. + */ + public ThemeCommand (String themeColour) { + requireNonNull(themeColour); + this.themeColour = themeColour; + } + + @Override + public CommandResult execute() { + + EventsCenter.getInstance().post(new ThemeSwitchRequestEvent(themeColour)); + return new CommandResult(String.format(MESSAGE_SUCCESS, themeColour)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ThemeCommand // instanceof handles nulls + && themeColour.equals(((ThemeCommand) other).themeColour)); + } +} +``` +###### \java\seedu\address\logic\parser\AddCommandParser.java +``` java + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an AddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_BIRTHDAY, + PREFIX_LEVEL_OF_FRIENDSHIP, PREFIX_UNIT_NUMBER, PREFIX_CCA, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_BIRTHDAY, PREFIX_PHONE, PREFIX_UNIT_NUMBER, + PREFIX_LEVEL_OF_FRIENDSHIP, PREFIX_UNIT_NUMBER) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + } + + try { + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME)).get(); + Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE)).get(); + Birthday birthday = ParserUtil.parseBirthday(argMultimap.getValue(PREFIX_BIRTHDAY)).get(); + UnitNumber unitNumber = ParserUtil.parseUnitNumber(argMultimap.getValue(PREFIX_UNIT_NUMBER)).get(); + LevelOfFriendship levelOfFriendship = ParserUtil.parseLevelOfFriendship(argMultimap + .getValue(PREFIX_LEVEL_OF_FRIENDSHIP)).get(); + Set ccaList = ParserUtil.parseCcas(argMultimap.getAllValues(PREFIX_CCA)); + Meet meetDate = new Meet(""); + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + Person person = new Person(name, phone, birthday, levelOfFriendship, unitNumber, ccaList, meetDate, + tagList); + return new AddCommand(person); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + +``` +###### \java\seedu\address\logic\parser\AddGoalCommandParser.java +``` java +/** + * Parses input arguments and creates a new AddGoalCommand object + */ +public class AddGoalCommandParser implements Parser { + + public static final String EMPTY_END_DATE_TIME = ""; + public static final boolean INITIAL_COMPLETION_STATUS = false; + /** + * Parses the given {@code String} of arguments in the context of the AddGoalCommand + * and returns an AddGoalCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddGoalCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_IMPORTANCE, PREFIX_GOAL_TEXT); + + if (!arePrefixesPresent(argMultimap, PREFIX_IMPORTANCE, PREFIX_GOAL_TEXT) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddGoalCommand.MESSAGE_USAGE)); + } + + try { + Importance importance = ParserUtil.parseImportance(argMultimap.getValue(PREFIX_IMPORTANCE)).get(); + GoalText goalText = ParserUtil.parseGoalText(argMultimap.getValue(PREFIX_GOAL_TEXT)).get(); + StartDateTime startDateTime = new StartDateTime(LocalDateTime.now()); + EndDateTime endDateTime = new EndDateTime(EMPTY_END_DATE_TIME); + Completion completion = new Completion(INITIAL_COMPLETION_STATUS); + Goal goal = new Goal(importance, goalText, startDateTime, endDateTime, completion); + return new AddGoalCommand(goal); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} +``` +###### \java\seedu\address\logic\parser\CompleteGoalCommandParser.java +``` java +/** + * Parses input arguments and creates a new CompleteGoalCommand object + */ +public class CompleteGoalCommandParser implements Parser { + + public static final boolean COMPLETED_BOOLEAN_VALUE = true; + + /** + * Parses the given {@code String} of arguments in the context of the CompleteGoalCommand + * and returns an CompleteGoalCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CompleteGoalCommand parse(String args) throws ParseException { + + Index index; + try { + index = ParserUtil.parseIndex(args); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, CompleteGoalCommand.MESSAGE_USAGE)); + } + + CompleteGoalDescriptor completeGoalDescriptor = new CompleteGoalDescriptor(); + + Completion completion = new Completion(COMPLETED_BOOLEAN_VALUE); + EndDateTime endDateTime = new EndDateTime(properDateTimeFormat(LocalDateTime.now())); + completeGoalDescriptor.setCompletion(completion); + completeGoalDescriptor.setEndDateTime(endDateTime); + + + return new CompleteGoalCommand(index, completeGoalDescriptor); + } +} +``` +###### \java\seedu\address\logic\parser\DateTimeParser.java +``` java +/** + * Contains utility methods used for parsing DateTime in the various *Parser classes. + */ +public class DateTimeParser { + + private static final int BEGIN_INDEX = 6; + /** + * Parses user input String specified{@code args} into LocalDateTime objects + * + * @return Empty Optional if args could not be parsed + * @Disclaimer : The parser used is a NLP API called 'natty' developed by 'Joe Stelmach' + */ + public static Optional nattyDateAndTimeParser(String args) { + if (args == null || args.isEmpty()) { + return Optional.empty(); + } + + Parser parser = new Parser(); + List groups = parser.parse(args); + + //Cannot be parsed + if (groups.size() <= 0) { + return Optional.empty(); + } + + DateGroup dateGroup = (DateGroup) groups.get(0); + if (dateGroup.getDates().size() < 0) { + return Optional.empty(); + } + + Date date = dateGroup.getDates().get(0); + + LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); + return Optional.ofNullable(localDateTime); + } + + /** + * Receives a LocalDateTime and formats the {@code dateTime} + * + * @return a formatted dateTime in String + */ + public static String properDateTimeFormat(LocalDateTime dateTime) { + StringBuilder builder = new StringBuilder(); + int day = dateTime.getDayOfMonth(); + String month = dateTime.getMonth().getDisplayName(TextStyle.FULL, Locale.ENGLISH); + int year = dateTime.getYear(); + int hour = dateTime.getHour(); + int minute = dateTime.getMinute(); + builder.append("Date: ") + .append(day) + .append(" ") + .append(month) + .append(" ") + .append(year) + .append(", Time: ") + .append(String.format("%02d", hour)) + .append(":") + .append(String.format("%02d", minute)); + return builder.toString(); + } + + public static LocalDateTime getLocalDateTimeFromProperDateTime(String properDateTimeString) { + String trimmedArgs = properDateTimeString.trim(); + int size = trimmedArgs.length(); + String stringFormat = properDateTimeString.substring(BEGIN_INDEX, size); + stringFormat = stringFormat.replace(", Time: ", ""); + return nattyDateAndTimeParser(stringFormat).get(); + } + + /** + * Receives a LocalDateTime and formats the {@code dateTime} + * + * @return a formatted dateTime in String + */ + public static String properReminderDateTimeFormat(LocalDateTime dateTime) { + StringBuilder builder = new StringBuilder(); + int day = dateTime.getDayOfMonth(); + String month = dateTime.getMonth().getDisplayName(TextStyle.FULL, Locale.ENGLISH); + int year = dateTime.getYear(); + int hour = dateTime.getHour(); + int minute = dateTime.getMinute(); + builder.append(day) + .append("/") + .append(month) + .append("/") + .append(year) + .append(" ") + .append(String.format("%02d", hour)) + .append(":") + .append(String.format("%02d", minute)); + return builder.toString(); + } + + public static boolean containsDateAndTime(String args) { + return nattyDateAndTimeParser(args).isPresent(); + } + + public static LocalDateTime getLocalDateTimeFromString(String dateString) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + LocalDateTime dateTime = LocalDateTime.parse(dateString, formatter); + return dateTime; + } +} +``` +###### \java\seedu\address\logic\parser\DeleteGoalCommandParser.java +``` java +/** + * Parses input arguments and creates a new DeleteGoalCommand object + */ +public class DeleteGoalCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteGoalCommand + * and returns an DeleteGoalCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteGoalCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteGoalCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteGoalCommand.MESSAGE_USAGE)); + } + } + +} +``` +###### \java\seedu\address\logic\parser\EditCommandParser.java +``` java + /** + * Parses {@code Collection ccas} into a {@code Set} if {@code ccas} is non-empty. + * If {@code ccas} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero ccas. + */ + private Optional> parseCcasForEdit(Collection ccas) throws IllegalValueException { + assert ccas != null; + + if (ccas.isEmpty()) { + return Optional.empty(); + } + Collection ccaSet = ccas.size() == 1 && ccas.contains("") ? Collections.emptySet() : ccas; + return Optional.of(ParserUtil.parseCcas(ccaSet)); + } + +``` +###### \java\seedu\address\logic\parser\EditGoalCommandParser.java +``` java +/** + * Parses input arguments and creates a new EditGoalCommand object + */ +public class EditGoalCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditGoalCommand + * and returns an EditGoalCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public EditGoalCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_GOAL_TEXT, PREFIX_IMPORTANCE); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditGoalCommand.MESSAGE_USAGE)); + } + + EditGoalDescriptor editGoalDescriptor = new EditGoalDescriptor(); + try { + ParserUtil.parseGoalText(argMultimap.getValue(PREFIX_GOAL_TEXT)).ifPresent(editGoalDescriptor::setGoalText); + ParserUtil.parseImportance(argMultimap.getValue(PREFIX_IMPORTANCE)) + .ifPresent(editGoalDescriptor::setImportance); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + + if (!editGoalDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditGoalCommand.MESSAGE_NOT_EDITED); + } + + return new EditGoalCommand(index, editGoalDescriptor); + } +} +``` +###### \java\seedu\address\logic\parser\OngoingGoalCommandParser.java +``` java +/** + * Parses input arguments and creates a new OngoingGoalCommand object + */ +public class OngoingGoalCommandParser implements Parser { + + public static final boolean ONGOING_BOOLEAN_VALUE = false; + /** + * Parses the given {@code String} of arguments in the context of the OngoingGoalCommand + * and returns an OngoingGoalCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public OngoingGoalCommand parse(String args) throws ParseException { + + Index index; + try { + index = ParserUtil.parseIndex(args); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, OngoingGoalCommand.MESSAGE_USAGE)); + } + + OngoingGoalDescriptor ongoingGoalDescriptor = new OngoingGoalDescriptor(); + + Optional empty = Optional.empty(); + Completion completion = new Completion(ONGOING_BOOLEAN_VALUE); + EndDateTime endDateTime = new EndDateTime(""); + ongoingGoalDescriptor.setCompletion(completion); + ongoingGoalDescriptor.setEndDateTime(endDateTime); + + + return new OngoingGoalCommand(index, ongoingGoalDescriptor); + } +} +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String birthday} into an {@code birthday}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code address} is invalid. + */ + public static Birthday parseBirthday(String birthday) throws IllegalValueException { + requireNonNull(birthday); + String trimmedBirthday = birthday.trim(); + if (!Birthday.isValidBirthday(trimmedBirthday)) { + throw new IllegalValueException(Birthday.MESSAGE_BIRTHDAY_CONSTRAINTS); + } + return new Birthday(trimmedBirthday); + } + + /** + * Parses a {@code Optional birthday} into an {@code Optional} if {@code birthday} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseBirthday(Optional birthday) throws IllegalValueException { + requireNonNull(birthday); + return birthday.isPresent() ? Optional.of(parseBirthday(birthday.get())) : Optional.empty(); + } + + /** + * Parses a {@code String levelOfFriendship} into a {@code LevelOfFriendship}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code levelOfFriendship} is invalid. + */ + public static LevelOfFriendship parseLevelOfFriendship(String levelOfFriendship) throws IllegalValueException { + requireNonNull(levelOfFriendship); + String trimmedLevelOfFriendship = levelOfFriendship.trim(); + if (!LevelOfFriendship.isValidLevelOfFriendship(trimmedLevelOfFriendship)) { + throw new IllegalValueException(LevelOfFriendship.MESSAGE_LEVEL_OF_FRIENDSHIP_CONSTRAINTS); + } + return new LevelOfFriendship(trimmedLevelOfFriendship); + } + + /** + * Parses a {@code Optional name} into an {@code Optional} if {@code name} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseLevelOfFriendship(Optional levelOfFriendship) + throws IllegalValueException { + requireNonNull(levelOfFriendship); + return levelOfFriendship.isPresent() ? Optional.of(parseLevelOfFriendship(levelOfFriendship.get())) + : Optional.empty(); + } + + /** + * Parses a {@code String unitNumber} into an {@code UnitNumber}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code unitNumber} is invalid. + */ + public static UnitNumber parseUnitNumber(String unitNumber) throws IllegalValueException { + requireNonNull(unitNumber); + String trimmedUnitNumber = unitNumber.trim(); + if (!UnitNumber.isValidUnitNumber(trimmedUnitNumber)) { + throw new IllegalValueException(UnitNumber.MESSAGE_UNIT_NUMBER_CONSTRAINTS); + } + return new UnitNumber(trimmedUnitNumber); + } + + /** + * Parses a {@code Optional unitNumber} into an {@code Optional} if {@code unitNumber} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseUnitNumber(Optional unitNumber) throws IllegalValueException { + requireNonNull(unitNumber); + return unitNumber.isPresent() ? Optional.of(parseUnitNumber(unitNumber.get())) : Optional.empty(); + } +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String cca} into a {@code Cca} + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code cca} is invalid. + */ + public static Cca parseCca(String cca) throws IllegalValueException { + requireNonNull(cca); + String trimmedCca = cca.trim(); + if (!Cca.isValidCcaName(trimmedCca)) { + throw new IllegalValueException(Cca.MESSAGE_CCA_CONSTRAINTS); + } + return new Cca(trimmedCca); + } + + /** + * Parses {@code Collection ccas} into a {@code Set}. + */ + public static Set parseCcas(Collection ccas) throws IllegalValueException { + requireNonNull(ccas); + final Set ccaSet = new HashSet<>(); + for (String ccaName : ccas) { + ccaSet.add(parseCca(ccaName)); + } + return ccaSet; + } + +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String importance} into an {@code Importance}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code importance} is invalid. + */ + public static Importance parseImportance(String importance) throws IllegalValueException { + requireNonNull(importance); + String trimmedImportance = importance.trim(); + if (!Importance.isValidImportance(trimmedImportance)) { + throw new IllegalValueException(Importance.MESSAGE_IMPORTANCE_CONSTRAINTS); + } + return new Importance(trimmedImportance); + } + + /** + * Parses a {@code Optional importance} into an {@code Optional} if {@code importance} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseImportance(Optional importance) throws IllegalValueException { + requireNonNull(importance); + return importance.isPresent() ? Optional.of(parseImportance(importance.get())) : Optional.empty(); + } + + /** + * Parses a {@code String goalText} into an {@code GoalText}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code goalText} is invalid. + */ + public static GoalText parseGoalText(String goalText) throws IllegalValueException { + requireNonNull(goalText); + String trimmedGoalText = goalText.trim(); + if (!GoalText.isValidGoalText(trimmedGoalText)) { + throw new IllegalValueException(GoalText.MESSAGE_GOAL_TEXT_CONSTRAINTS); + } + return new GoalText(trimmedGoalText); + } + + /** + * Parses a {@code Optional goalText} into an {@code Optional} if {@code goalText} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseGoalText(Optional goalText) throws IllegalValueException { + requireNonNull(goalText); + return goalText.isPresent() ? Optional.of(parseGoalText(goalText.get())) : Optional.empty(); + } + +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String sortField} and checks if it is a valid sortField parameter. + * + * @throws IllegalValueException if specified String is an invalid field. + */ + public static String parseSortGoalField(String sortField) throws IllegalValueException { + String trimmedSortField = sortField.trim(); + switch (trimmedSortField) { + case "importance": + case "completion": + case "startdatetime": + return trimmedSortField; + default: + throw new IllegalValueException(MESSAGE_INVALID_SORT_FIELD); + } + } + + /** + * Parses a {@code Optional sortField} into an {@code Optional} if {@code sortField} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseSortGoalField(Optional sortField) throws IllegalValueException { + requireNonNull(sortField); + return sortField.isPresent() ? Optional.of(parseSortGoalField(sortField.get())) : Optional.empty(); + } + + /** + * Parses a {@code String order} and check if it is a valid order parameter. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code order} is invalid. + */ + public static String parseSortGoalOrder(String order) throws IllegalValueException { + requireNonNull(order); + String trimmedOrder = order.trim(); + switch (trimmedOrder) { + case "ascending": + case "descending": + return trimmedOrder; + default: + throw new IllegalValueException(MESSAGE_INVALID_ORDER_FIELD); + } + } + + /** + * Parses a {@code Optional sortOrder} into an {@code Optional} if {@code sortOrder} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseSortGoalOrder(Optional sortOrder) throws IllegalValueException { + requireNonNull(sortOrder); + return sortOrder.isPresent() ? Optional.of(parseSortGoalOrder(sortOrder.get())) : Optional.empty(); + } +} +``` +###### \java\seedu\address\logic\parser\SortGoalCommandParser.java +``` java +/** + * Parses input arguments and creates a new SortGoalCommand object + */ +public class SortGoalCommandParser implements Parser { + + @Override + public SortGoalCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_SORT_FIELD, PREFIX_SORT_ORDER); + if (!arePrefixesPresent(argMultimap, PREFIX_SORT_FIELD, PREFIX_SORT_ORDER) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortGoalCommand.MESSAGE_USAGE)); + } + try { + String sortField = ParserUtil.parseSortGoalField(argMultimap.getValue(PREFIX_SORT_FIELD)).get(); + String sortOrder = ParserUtil.parseSortGoalOrder(argMultimap.getValue(PREFIX_SORT_ORDER)).get(); + return new SortGoalCommand(sortField, sortOrder); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortGoalCommand.MESSAGE_USAGE)); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} +``` +###### \java\seedu\address\logic\parser\ThemeCommandParser.java +``` java +/** + * Parses input arguments and creates a new ThemeCommand object + */ +public class ThemeCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ThemeCommand + * and returns a ThemeCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ThemeCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ThemeCommand.MESSAGE_USAGE)); + } + if (!isValidThemeColour(trimmedArgs)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ThemeCommand.MESSAGE_INVALID_THEME_COLOUR)); + + } + return new ThemeCommand(trimmedArgs); + } + + /** + * + * @param themeColour + * @return + */ + private boolean isValidThemeColour(String themeColour) { + HashMap themes = getThemeHashMap(); + if (themes.containsKey(themeColour.toLowerCase())) { + return true; + } else { + return false; + } + + } +} + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + public void setCcas(Set ccas) { + this.ccas.setCcas(ccas); } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + public void setGoals(List goals) throws DuplicateGoalException { + this.goals.setGoals(goals); + } + + + public void setReminders(List reminders) throws DuplicateReminderException { + this.reminders.setReminders(reminders); + } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Removes all {@code Ccas}s that are not used by any {@code Person} in this {@code AddressBook}. + */ + private void removeUnusedCcas() { + Set ccasInPersons = persons.asObservableList().stream() + .map(Person::getCcas) + .flatMap(Set::stream) + .collect(Collectors.toSet()); + ccas.setCcas(ccasInPersons); + } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Updates the master cca list to include ccas in {@code person} that are not in the list. + * @return a copy of this {@code person} such that every cca in this person points to a Cca object in the master + * list. + */ + private Person syncWithMasterCcaList(Person person) { + final UniqueCcaList personCcas = new UniqueCcaList(person.getCcas()); + ccas.mergeFrom(personCcas); + + // Create map with values = cca object references in the master list + // used for checking person cca references + final Map masterCcaObjects = new HashMap<>(); + ccas.forEach(cca -> masterCcaObjects.put(cca, cca)); + + // Rebuild the list of person tags to point to the relevant tags in the master tag list. + final Set correctCcaReferences = new HashSet<>(); + personCcas.forEach(cca -> correctCcaReferences.add(masterCcaObjects.get(cca))); + return new Person( + person.getName(), person.getPhone(), person.getBirthday(), + person.getLevelOfFriendship(), person.getUnitNumber(), correctCcaReferences, person.getMeetDate(), + person.getTags()); + } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + public void addCca(Cca cca) throws UniqueCcaList.DuplicateCcaException { + ccas.add(cca); + } + + //// tag-level operations + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Adds a goal to CollegeZone. + * @throws DuplicateGoalException if an equivalent goal already exists. + */ + public void addGoal(Goal g) throws DuplicateGoalException { + goals.add(g); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * @throws GoalNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removeGoal(Goal key) throws GoalNotFoundException { + if (goals.remove(key)) { + return true; + } else { + throw new GoalNotFoundException(); + } + } + + /** + * Replaces the given goal {@code target} in the list with {@code editedGoal}. + * + * @throws DuplicateGoalException if updating the goal's details causes the goal to be equivalent to + * another existing goal in the list. + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + public void updateGoal(Goal target, Goal editedGoal) + throws DuplicateGoalException, GoalNotFoundException { + requireNonNull(editedGoal); + goals.setGoal(target, editedGoal); + } + + /** + * Replaces the given goal {@code target} in the list with {@code editedGoal}. + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + public void updateGoalWithoutParameters(Goal target, Goal editedGoal) throws GoalNotFoundException { + requireNonNull(editedGoal); + goals.setGoalWithoutParameters(target, editedGoal); + } + + /** + * Sorts goal based on the sort field and sort order input. + */ + public void sortGoal(String sortField, String sortOrder) throws EmptyGoalListException { + requireNonNull(sortField); + requireNonNull(sortOrder); + if (goals.getSize() > 0) { + goals.sortGoal(sortField, sortOrder); + } else { + throw new EmptyGoalListException(); + } + } + //// reminder-level operations + +``` +###### \java\seedu\address\model\goal\Completion.java +``` java +/** + * Represents a Goal's completion status in the Goals Page. + */ +public class Completion { + public final String value; + public final boolean hasCompleted; + + /** + * Constructs a {@code Completion}. + * + * @param isCompleted A valid boolean. + */ + public Completion(Boolean isCompleted) { + requireNonNull(isCompleted); + if (isCompleted) { + this.hasCompleted = true; + this.value = "true"; + } else { + this.hasCompleted = false; + this.value = "false"; + } + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Completion // instanceof handles nulls + && this.value.equals(((Completion) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} +``` +###### \java\seedu\address\model\goal\EndDateTime.java +``` java +/** + * Represents a Goal's end date and time in the Goals Page. + * Guarantees: immutable; is valid + */ +public class EndDateTime { + + public final String value; + public final LocalDateTime localDateTimeValue; + /** + * Constructs a {@code EndDateTime}. + * + * @param endDateTime A valid endDateTime number. + */ + public EndDateTime(String endDateTime) { + if (endDateTime.equals("")) { + this.value = ""; + this.localDateTimeValue = null; + } else { + LocalDateTime localEndDateTime = nattyDateAndTimeParser(endDateTime).get(); + this.value = properDateTimeFormat(localEndDateTime); + this.localDateTimeValue = localEndDateTime; + } + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EndDateTime // instanceof handles nulls + && this.value.equals(((EndDateTime) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} +``` +###### \java\seedu\address\model\goal\exceptions\DuplicateGoalException.java +``` java +/** + * Signals that the operation will result in duplicate Goal objects. + */ +public class DuplicateGoalException extends DuplicateDataException { + public DuplicateGoalException() { + super("Operation would result in duplicate goals"); + } +} +``` +###### \java\seedu\address\model\goal\exceptions\EmptyGoalListException.java +``` java +/** + * Signals that the current goal list is empty. + */ +public class EmptyGoalListException extends Exception { +} +``` +###### \java\seedu\address\model\goal\exceptions\GoalNotFoundException.java +``` java +/** + * Signals that the operation is unable to find the specified goal. + */ +public class GoalNotFoundException extends Exception {} +``` +###### \java\seedu\address\model\goal\Goal.java +``` java +/** + * Represents a Goal in the Goals Page. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Goal { + + private final Importance importance; + private final GoalText goalText; + private final StartDateTime startDateTime; + private final EndDateTime endDateTime; + private final Completion completion; + + /** + * Every field must be present and not null. + */ + + public Goal(Importance importance, GoalText goalText, StartDateTime startDateTime, + EndDateTime endDateTime, Completion completion) { + requireAllNonNull(importance, goalText, startDateTime, completion); + this.importance = importance; + this.goalText = goalText; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.completion = completion; + } + + public Importance getImportance() { + return importance; + } + + public GoalText getGoalText() { + return goalText; + } + + public StartDateTime getStartDateTime() { + return startDateTime; + } + + public EndDateTime getEndDateTime() { + return endDateTime; + } + + public Completion getCompletion() { + return completion; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Goal)) { + return false; + } + + Goal otherPerson = (Goal) other; + return otherPerson.getImportance().equals(this.getImportance()) + && otherPerson.getGoalText().equals(this.getGoalText()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(importance, goalText, startDateTime); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(" Importance: ") + .append(getImportance()) + .append(" Goal Text: ") + .append(getGoalText()) + .append(" Start Date Time: ") + .append(getStartDateTime()); + + return builder.toString(); + } + +} +``` +###### \java\seedu\address\model\goal\GoalText.java +``` java +/** + * Represents a Goal's text in the Goals Page. + * Guarantees: immutable; is valid as declared in {@link #isValidGoalText(String)} + */ +public class GoalText { + + + public static final String MESSAGE_GOAL_TEXT_CONSTRAINTS = + "Goal text can be any expression that are not just whitespaces."; + public static final String GOAL_TEXT_VALIDATION_REGEX = "^(?!\\s*$).+"; + public final String value; + + /** + * Constructs a {@code GoalText}. + * + * @param goalText A valid goal text. + */ + public GoalText(String goalText) { + requireNonNull(goalText); + checkArgument(isValidGoalText(goalText), MESSAGE_GOAL_TEXT_CONSTRAINTS); + this.value = goalText; + } + + /** + * Returns true if a given string is a valid goal text. + */ + public static boolean isValidGoalText(String test) { + return test.matches(GOAL_TEXT_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GoalText // instanceof handles nulls + && this.value.equals(((GoalText) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} +``` +###### \java\seedu\address\model\goal\Importance.java +``` java +/** + * Represents a Goal's importance in CollegeZone. + * Guarantees: immutable; is valid as declared in {@link #isValidImportance(String)} + */ +public class Importance implements Comparable { + + + public static final String MESSAGE_IMPORTANCE_CONSTRAINTS = + "Importance should only be a numerical integer value between 1 to 10."; + public static final String IMPORTANCE_VALIDATION_REGEX = "[0-9]+"; + private static final int MINIMUM_IMPORTANCE = 1; + private static final int MAXIMUM_IMPORTANCE = 10; + private static int importanceInIntegerForm; + public final String value; + + /** + * Constructs a {@code Importance}. + * + * @param importance A valid importance. + */ + public Importance(String importance) { + requireNonNull(importance); + checkArgument(isValidImportance(importance), MESSAGE_IMPORTANCE_CONSTRAINTS); + this.value = importance; + } + + /** + * Returns true if a given string is a valid goal importance. + */ + public static boolean isValidImportance(String test) { + return test.matches(IMPORTANCE_VALIDATION_REGEX) && isAnIntegerWithinRange(test); + } + + /** + * Returns true if a given string is an integer and within range of importance. + */ + private static boolean isAnIntegerWithinRange(String test) { + importanceInIntegerForm = Integer.parseInt(test); + if (importanceInIntegerForm >= MINIMUM_IMPORTANCE + && importanceInIntegerForm <= MAXIMUM_IMPORTANCE) { + return true; + } else { + return false; + } + } + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Importance // instanceof handles nulls + && this.value.equals(((Importance) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public int compareTo(Importance importance) { + if ((Integer.valueOf(importance.value)).equals(Integer.valueOf(this.value))) { + return 0; + } else if ((Integer.valueOf(importance.value)) < (Integer.valueOf(this.value))) { + return 1; + } else if ((Integer.valueOf(importance.value)) > (Integer.valueOf(this.value))) { + return -1; + } + return 0; + } +} +``` +###### \java\seedu\address\model\goal\StartDateTime.java +``` java +/** + * Represents a Goal's start date in the address book. + */ +public class StartDateTime implements Comparable { + + public final String value; + public final LocalDateTime localDateTimeValue; + + + /** + * Constructs a {@code StartDateTime}. + * + * @param startDateTime A valid LocalDateTime. + */ + public StartDateTime(LocalDateTime startDateTime) { + requireNonNull(startDateTime); + this.localDateTimeValue = startDateTime; + this.value = properDateTimeFormat(startDateTime); + } + + public StartDateTime(String startDateTimeInString) { + requireNonNull(startDateTimeInString); + this.value = startDateTimeInString; + this.localDateTimeValue = getLocalDateTimeFromProperDateTime(startDateTimeInString); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof StartDateTime // instanceof handles nulls + && this.value.equals(((StartDateTime) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public int compareTo(StartDateTime startDateTime) { + if ((startDateTime.localDateTimeValue).isBefore(this.localDateTimeValue)) { + return 1; + } else if ((startDateTime.localDateTimeValue).isAfter(this.localDateTimeValue)) { + return -1; + } + return 0; + } +} +``` +###### \java\seedu\address\model\goal\UniqueGoalList.java +``` java +/** + * A list of goals that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Goal#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class UniqueGoalList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent goal as the given argument. + */ + public boolean contains(Goal toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a goal to the list. + * + * @throws DuplicateGoalException if the goal to add is a duplicate of an existing goal in the list. + */ + public void add(Goal toAdd) throws DuplicateGoalException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateGoalException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the goal {@code target} in the list with {@code editedGoal}. + * + * @throws DuplicateGoalException if the replacement is equivalent to another existing goal in the list. + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + public void setGoal(Goal target, Goal editedGoal) + throws DuplicateGoalException, GoalNotFoundException { + requireNonNull(editedGoal); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new GoalNotFoundException(); + } + + if (!target.equals(editedGoal) && internalList.contains(editedGoal)) { + throw new DuplicateGoalException(); + } + + internalList.set(index, editedGoal); + } + + /** + * Replaces the goal {@code target} in the list with {@code editedGoal}. + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + public void setGoalWithoutParameters(Goal target, Goal editedGoal) + throws GoalNotFoundException { + requireNonNull(editedGoal); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new GoalNotFoundException(); + } + + internalList.set(index, editedGoal); + } + + /** + * Removes the equivalent goal from the list. + * + * @throws GoalNotFoundException if no such goal could be found in the list. + */ + public boolean remove(Goal toRemove) throws GoalNotFoundException { + requireNonNull(toRemove); + final boolean goalFoundAndDeleted = internalList.remove(toRemove); + if (!goalFoundAndDeleted) { + throw new GoalNotFoundException(); + } + return goalFoundAndDeleted; + } + + public void setGoals(UniqueGoalList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setGoals(List goals) throws DuplicateGoalException { + requireAllNonNull(goals); + final UniqueGoalList replacement = new UniqueGoalList(); + for (final Goal goal : goals) { + replacement.add(goal); + } + setGoals(replacement); + } + + /** + * Returns the size of goal list. + */ + public int getSize() { + return internalList.size(); + } + + /** + * Sort goals internal list using comparator + * @param sortField + */ + public void sortGoal(String sortField, String sortOrder) throws EmptyGoalListException { + String sortFieldAndOrder = sortField + " " + sortOrder; + switch (sortFieldAndOrder) { + case "importance ascending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) ->goalA.getImportance() + .compareTo(goalB.getImportance())); + break; + case "importance descending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) ->goalB.getImportance() + .compareTo(goalA.getImportance())); + break; + case "completion ascending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) -> new Boolean(goalA.getCompletion().hasCompleted) + .compareTo(goalB.getCompletion().hasCompleted)); + break; + case "completion descending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) -> new Boolean(goalB.getCompletion().hasCompleted) + .compareTo(goalA.getCompletion().hasCompleted)); + break; + case "startdatetime ascending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) ->goalA.getStartDateTime() + .compareTo(goalB.getStartDateTime())); + break; + case "startdatetime descending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) ->goalB.getStartDateTime() + .compareTo(goalA.getStartDateTime())); + break; + + default: + break; + } + } + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueGoalList // instanceof handles nulls + && this.internalList.equals(((UniqueGoalList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} +``` +###### \java\seedu\address\model\Model.java +``` java + /** Adds the given goal */ + void addGoal(Goal goal) throws DuplicateGoalException; + + /** Returns an unmodifiable view of the filtered goal list */ + ObservableList getFilteredGoalList(); + + /** + * Replaces the given goal {@code target} with {@code editedGoal}. + * + * @throws DuplicateGoalException if updating the goal's details causes the goal to be equivalent to + * another existing goal in the list. + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + void updateGoal(Goal target, Goal editedGoal) + throws DuplicateGoalException, GoalNotFoundException; + + /** + * Updates the filter of the filtered goal list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredGoalList(Predicate predicate); + + /** Deletes the given goal. */ + void deleteGoal(Goal target) throws GoalNotFoundException; + + /** + * Replaces the given goal {@code target} with {@code updateGoal}. + * + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + void updateGoalWithoutParameters(Goal target, Goal editedGoal) throws GoalNotFoundException; + + /** + * Sort the goal based on sortType + */ + void sortGoal(String sortType, String sortOrder) throws EmptyGoalListException; + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public void addGoal(Goal goal) throws DuplicateGoalException { + addressBook.addGoal(goal); + updateFilteredGoalList(PREDICATE_SHOW_ALL_GOALS); + indicateAddressBookChanged(); + } + + @Override + public ObservableList getFilteredGoalList() { + return FXCollections.unmodifiableObservableList(filteredGoals); + } + + @Override + public void updateFilteredGoalList(Predicate predicate) { + requireNonNull(predicate); + filteredGoals.setPredicate(predicate); + } + + @Override + public synchronized void deleteGoal(Goal target) throws GoalNotFoundException { + addressBook.removeGoal(target); + indicateAddressBookChanged(); + } + + @Override + public void updateGoal(Goal target, Goal editedGoal) + throws DuplicateGoalException, GoalNotFoundException { + requireAllNonNull(target, editedGoal); + + addressBook.updateGoal(target, editedGoal); + indicateAddressBookChanged(); + } + + @Override + public void updateGoalWithoutParameters(Goal target, Goal editedGoal) + throws GoalNotFoundException { + requireAllNonNull(target, editedGoal); + + addressBook.updateGoalWithoutParameters(target, editedGoal); + indicateAddressBookChanged(); + } + + @Override + public void sortGoal(String sortGoalType, String sortGoalOrder) throws EmptyGoalListException { + requireAllNonNull(sortGoalType, sortGoalOrder); + addressBook.sortGoal(sortGoalType, sortGoalOrder); + indicateAddressBookChanged(); + } + +``` +###### \java\seedu\address\model\person\Birthday.java +``` java +/** + * Represents a Person's birthday in CollegeZone. + * Guarantees: immutable; is valid as declared in {@link #isValidBirthday(String)} + */ +public class Birthday { + + public static final String MESSAGE_BIRTHDAY_CONSTRAINTS = "Person birthday should be a valid date and cannot " + + "be later than today's date."; + public static final String BIRTHDAY_VALIDATION_REGEX = + "^(((0[1-9]|[12]\\d|3[01])\\/(0[13578]|1[02])\\/((19|[2-9]\\d)\\d{2}))|((0[1-9]|[12]\\d|30)" + + "\\/(0[13456789]|1[012])\\/((19|[2-9]\\d)\\d{2}))|((0[1-9]|1\\d|2[0-8])\\/02\\/((19|[2-9]\\d)" + + "\\d{2}))|(29\\/02\\/((1[6-9]|[2-9]\\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|" + + "[3579][26])00))))$"; + + public final String value; + + /** + * Constructs an {@code Birthday}. + * + * @param birthday A valid birthday. + */ + public Birthday(String birthday) { + requireNonNull(birthday); + checkArgument(isValidBirthday(birthday), MESSAGE_BIRTHDAY_CONSTRAINTS); + this.value = birthday; + } + + /** + * Returns if a given string is a valid person birthday (before current date). + */ + public static boolean isValidBirthday(String test) { + LocalDateTime birthdayLocalDateTime = null; + LocalDateTime currentLocalDateTime = LocalDateTime.now(); + if (test.matches(BIRTHDAY_VALIDATION_REGEX)) { + String birthdayInDifferentFormat = getDifferentBirthdayFormat(test); + birthdayLocalDateTime = nattyDateAndTimeParser(birthdayInDifferentFormat).get(); + } else { + return false; + } + return isBeforeCurrentDate(birthdayLocalDateTime, currentLocalDateTime); + } + + /** + * Takes in @param birthdayLocalDateTime and @param currentLocalDateTime and checks if 1st parameter is later + * than the second parameter + * @return boolean + */ + private static boolean isBeforeCurrentDate(LocalDateTime birthdayLocalDateTime, + LocalDateTime currentLocalDateTime) { + if (birthdayLocalDateTime.isBefore(currentLocalDateTime)) { + return true; + } else { + return false; + } + } + /** + * + * Takes in @param date in dd/mm/yyyy format + * @return birthday string in mm/dd/yyyy format + */ + public static String getDifferentBirthdayFormat(String date) { + String day = date.substring(0, 2); + String month = date.substring(3, 5); + String year = date.substring(6, 10); + StringBuilder builder = new StringBuilder(); + builder.append(month) + .append("/") + .append(day) + .append("/") + .append(year); + return builder.toString(); + } + +``` +###### \java\seedu\address\model\person\Birthday.java +``` java + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Birthday // instanceof handles nulls + && this.value.equals(((Birthday) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} +``` +###### \java\seedu\address\model\person\Cca.java +``` java +/** + * Represents a Person's CCAs in CollegeZone. + * Guarantees: immutable; is valid as declared in {@link #isValidCcaName(String)} + */ +public class Cca { + + public static final String MESSAGE_CCA_CONSTRAINTS = "CCAs should be in alphanumeric"; + public static final String CCA_VALIDATION_REGEX = "\\s*\\p{Alnum}[\\p{Alnum}\\s]*"; + public final String ccaName; + + /** + * Constructs a {@code CCA}. + * + * @param ccaName A valid CCA. + */ + public Cca(String ccaName) { + requireNonNull(ccaName); + checkArgument(isValidCcaName(ccaName), MESSAGE_CCA_CONSTRAINTS); + this.ccaName = ccaName; + } + + /** + * Returns true if a given string is a valid CCA name. + */ + public static boolean isValidCcaName(String test) { + return test.matches(CCA_VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Cca // instanceof handles nulls + && this.ccaName.equals(((Cca) other).ccaName)); // state check + } + + @Override + public int hashCode() { + return ccaName.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return '[' + ccaName + ']'; + } + +} +``` +###### \java\seedu\address\model\person\LevelOfFriendship.java +``` java +/** + * Represents a Person's Level of Friendship in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidLevelOfFriendship(String)} + */ +public class LevelOfFriendship { + + public static final String MESSAGE_LEVEL_OF_FRIENDSHIP_CONSTRAINTS = + "Level of Friendship should only be a numerical integer value between 1 to 10"; + public static final String LEVEL_OF_FRIENDSHIP_VALIDATION_REGEX = "[0-9]+"; + private static final int MINIMUM_LEVEL_OF_FRIENDSHIP = 1; + private static final int MAXIMUM_LEVEL_OF_FRIENDSHIP = 10; + private static int levelOfFriendshipInIntegerForm; + public final String value; + + /** + * * Constructs an {@code LevelOfFriendship}. + * + * @param levelOfFriendship A valid level of friendship number. + */ + public LevelOfFriendship(String levelOfFriendship) { + requireNonNull(levelOfFriendship); + checkArgument(isValidLevelOfFriendship(levelOfFriendship), MESSAGE_LEVEL_OF_FRIENDSHIP_CONSTRAINTS); + this.value = levelOfFriendship; + } + + /** + * Returns true if a given string is a valid person level of friendship. + */ + public static boolean isValidLevelOfFriendship(String test) { + + return test.matches(LEVEL_OF_FRIENDSHIP_VALIDATION_REGEX) && isAnIntegerWithinRange(test); + } + + /** + * Returns true if a given string is an integer and within range of level of friendship. + */ + private static boolean isAnIntegerWithinRange(String test) { + levelOfFriendshipInIntegerForm = Integer.parseInt(test); + if (levelOfFriendshipInIntegerForm >= MINIMUM_LEVEL_OF_FRIENDSHIP + && levelOfFriendshipInIntegerForm <= MAXIMUM_LEVEL_OF_FRIENDSHIP) { + return true; + } else { + return false; + } + } + + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LevelOfFriendship // instanceof handles nulls + && this.value.equals(((LevelOfFriendship) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} +``` +###### \java\seedu\address\model\person\UniqueCcaList.java +``` java +/** + * A list of ccas that enforces no nulls and uniqueness between its elements. + * + * Supports minimal set of list operations for the app's features. + * + * @see Cca#equals(Object) + */ +public class UniqueCcaList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Constructs empty CcaList. + */ + public UniqueCcaList() {} + + /** + * Creates a UniqueCcaList using given ccas. + * Enforces no nulls. + */ + public UniqueCcaList(Set ccas) { + requireAllNonNull(ccas); + internalList.addAll(ccas); + + assert CollectionUtil.elementsAreUnique(internalList); + } + + /** + * Returns all ccas in this list as a Set. + * This set is mutable and change-insulated against the internal list. + */ + public Set toSet() { + assert CollectionUtil.elementsAreUnique(internalList); + return new HashSet<>(internalList); + } + + /** + * Replaces the Ccas in this list with those in the argument cca list. + */ + public void setCcas(Set ccas) { + requireAllNonNull(ccas); + internalList.setAll(ccas); + assert CollectionUtil.elementsAreUnique(internalList); + } + + /** + * Ensures every tag in the argument list exists in this object. + */ + public void mergeFrom(UniqueCcaList from) { + final Set alreadyInside = this.toSet(); + from.internalList.stream() + .filter(cca -> !alreadyInside.contains(cca)) + .forEach(internalList::add); + + assert CollectionUtil.elementsAreUnique(internalList); + } + + /** + * Returns true if the list contains an equivalent Cca as the given argument. + */ + public boolean contains(Cca toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a Cca to the list. + * + * @throws DuplicateCcaException if the Cca to add is a duplicate of an existing Cca in the list. + */ + public void add(Cca toAdd) throws DuplicateCcaException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateCcaException(); + } + internalList.add(toAdd); + + assert CollectionUtil.elementsAreUnique(internalList); + } + + @Override + public Iterator iterator() { + assert CollectionUtil.elementsAreUnique(internalList); + return internalList.iterator(); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + assert CollectionUtil.elementsAreUnique(internalList); + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public boolean equals(Object other) { + assert CollectionUtil.elementsAreUnique(internalList); + return other == this // short circuit if same object + || (other instanceof UniqueCcaList // instanceof handles nulls + && this.internalList.equals(((UniqueCcaList) other).internalList)); + } + + /** + * Returns true if the element in this list is equal to the elements in {@code other}. + * The elements do not have to be in the same order. + */ + public boolean equalsOrderInsensitive(UniqueCcaList other) { + assert CollectionUtil.elementsAreUnique(internalList); + assert CollectionUtil.elementsAreUnique(other.internalList); + return this == other || new HashSet<>(this.internalList).equals(new HashSet<>(other.internalList)); + } + + @Override + public int hashCode() { + assert CollectionUtil.elementsAreUnique(internalList); + return internalList.hashCode(); + } + + /** + * Signals that an operation would have violated the 'no duplicates' property of the list. + */ + public static class DuplicateCcaException extends DuplicateDataException { + protected DuplicateCcaException() { + super("Operation would result in duplicate ccas"); + } + } + +} +``` +###### \java\seedu\address\model\person\UnitNumber.java +``` java + /** + * * Constructs an {@code UnitNumber}. + * + * @param unitNumber A valid unit number. + */ + public UnitNumber(String unitNumber) { + requireNonNull(unitNumber); + checkArgument(isValidUnitNumber(unitNumber), MESSAGE_UNIT_NUMBER_CONSTRAINTS); + this.value = unitNumber; + } + + /** + * Returns true if a given string is a valid unit number. + */ + public static boolean isValidUnitNumber(String test) { + + return test.matches(UNIT_NUMBER_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + +``` +###### \java\seedu\address\model\person\UnitNumber.java +``` java + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UnitNumber // instanceof handles nulls + && this.value.equals(((UnitNumber) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} +``` +###### \java\seedu\address\model\ReadOnlyAddressBook.java +``` java + /** + * Returns an unmodifiable view of the ccas list. + * This list will not contain any duplicate ccas. + */ + ObservableList getCcaList(); + +``` +###### \java\seedu\address\model\ReadOnlyAddressBook.java +``` java + /** + * Returns an unmodifiable view of the goals list. + * This list will not contain any duplicate goals. + */ + ObservableList getGoalList(); +``` +###### \java\seedu\address\model\ThemeColourUtil.java +``` java +/** + * Contains utility methods for ThemeColour. + */ +public class ThemeColourUtil { + + private static final HashMap themes; + + static { + themes = new HashMap<>(); + themes.put("light", "view/LightTheme.css"); + themes.put("bubblegum", "view/BubblegumTheme.css"); + themes.put("dark", "view/DarkTheme.css"); + } + + public static HashMap getThemeHashMap() { + return themes; + } +} +``` +###### \java\seedu\address\model\util\SampleCollegeZone.java +``` java +/** + * Contains method to get a sample CollegeZone data + */ +public class SampleCollegeZone { + + public static ReadOnlyAddressBook getSampleCollegeZone() { + AddressBook sampleCz = new AddressBook(); + try { + for (Person samplePerson : getSamplePersons()) { + sampleCz.addPerson(samplePerson); + } + } catch (DuplicatePersonException e) { + throw new AssertionError("sample data cannot contain duplicate persons", e); + } + try { + for (Goal sampleGoal : getSampleGoals()) { + sampleCz.addGoal(sampleGoal); + } + } catch (DuplicateGoalException e) { + throw new AssertionError("sample data cannot contain duplicate goals", e); + } + try { + for (Reminder sampleReminder : getSampleReminders()) { + sampleCz.addReminder(sampleReminder); + } + } catch (DuplicateReminderException e) { + throw new AssertionError("sample data cannot contain duplicate reminders", e); + } + return sampleCz; + } +} +``` +###### \java\seedu\address\model\util\SampleDataUtil.java +``` java +/** + * Contains utility methods for populating {@code CollegeZone} with sample data. + */ +public class SampleDataUtil { + + public static final Meet MEET_DATE = new Meet("15/04/2018"); + + public static Person[] getSamplePersons() { + return new Person[] { + new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Birthday("01/01/1997"), + new LevelOfFriendship("5"), new UnitNumber("#06-40"), getCcaSet("Basketball"), + MEET_DATE, getTagSet("friends", "RA")), + new Person(new Name("Bernice Yu"), new Phone("99272758"), new Birthday("21/02/1990"), + new LevelOfFriendship("9"), new UnitNumber("#07-18"), getCcaSet(), + new Meet("15/05/2018"), getTagSet("colleagues", "friends")), + new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Birthday("05/09/1980"), + new LevelOfFriendship("1"), new UnitNumber("#11-04"), getCcaSet("Swimming"), + new Meet("15/05/2018"), getTagSet("neighbours")), + new Person(new Name("David Li"), new Phone("91031282"), new Birthday("20/02/1995"), + new LevelOfFriendship("6"), new UnitNumber("#16-43"), getCcaSet(), + new Meet("16/04/2018"), getTagSet("family")), + new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Birthday("01/01/1999"), + new LevelOfFriendship("7"), new UnitNumber("#16-41"), getCcaSet(), + new Meet("17/04/2018"), getTagSet("classmates")), + new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Birthday("02/04/1995"), + new LevelOfFriendship("10"), new UnitNumber("#6-43"), getCcaSet("Computing club", "Anime Club"), + new Meet("18/04/2018"), getTagSet("colleagues")), + new Person(new Name("James Mee"), new Phone("98887555"), new Birthday("22/08/1992"), + new LevelOfFriendship("1"), new UnitNumber("#06-40"), getCcaSet("Tennis"), + new Meet("19/04/2018"), getTagSet("RA")), + new Person(new Name("Jane Ray"), new Phone("93336444"), new Birthday("25/09/1991"), + new LevelOfFriendship("1"), new UnitNumber("#07-40"), getCcaSet("Chess Club"), + new Meet("20/04/2018"), getTagSet("RA")), + new Person(new Name("Deborah Low"), new Phone("91162930"), new Birthday("24/05/1997"), + new LevelOfFriendship("9"), new UnitNumber("#10-24"), getCcaSet("Aerobics Club"), + new Meet("21/04/2018"), getTagSet("colleagues")), + new Person(new Name("Royce Lew"), new Phone("93265932"), new Birthday("10/04/1996"), + new LevelOfFriendship("5"), new UnitNumber("#02-021"), getCcaSet(), + new Meet("22/04/2018"), getTagSet("boyfriend")), + new Person(new Name("Kaden Yeo"), new Phone("82350332"), new Birthday("28/03/2001"), + new LevelOfFriendship("6"), new UnitNumber("#6-20"), getCcaSet("shooting"), + new Meet("23/04/2018"), getTagSet("friends")), + new Person(new Name("Matthew Chiang"), new Phone("92624417"), new Birthday("02/04/1995"), + new LevelOfFriendship("4"), new UnitNumber("#20-43"), getCcaSet("Anime Club"), + new Meet("24/04/2018"), getTagSet("classmate")), + new Person(new Name("Loh Sin Yuen"), new Phone("92624417"), new Birthday("02/05/1995"), + new LevelOfFriendship("10"), new UnitNumber("#03-63"), getCcaSet("dance"), + new Meet("25/04/2018"), getTagSet("schoolmate")), + new Person(new Name("Florence Chiang"), new Phone("92624417"), new Birthday("02/06/1995"), + new LevelOfFriendship("10"), new UnitNumber("#6-97"), getCcaSet("volleyball"), + new Meet("26/04/2018"), getTagSet("bff")), + new Person(new Name("Daniel Low"), new Phone("92624417"), new Birthday("12/04/1995"), + new LevelOfFriendship("1"), new UnitNumber("#7-473"), getCcaSet("Muay Thai"), + new Meet("27/04/2018"), getTagSet("cousin")), + new Person(new Name("Rachel Lee Yan Ling"), new Phone("92624417"), new Birthday("23/04/1995"), + new LevelOfFriendship("3"), new UnitNumber("#6-69"), getCcaSet("Computing club", "Anime Club"), + new Meet("28/04/2018"), getTagSet("cousin")), + new Person(new Name("Sarah tan"), new Phone("92624417"), new Birthday("27/04/1999"), + new LevelOfFriendship("2"), new UnitNumber("#8-43"), getCcaSet("Computing club", "Anime Club"), + new Meet("28/04/2018"), getTagSet()), + new Person(new Name("Amanda Soh"), new Phone("92624417"), new Birthday("02/12/1995"), + new LevelOfFriendship("1"), new UnitNumber("#24-579"), getCcaSet("Computing club", "Anime Club"), + new Meet("17/06/2018"), getTagSet("exgirlfriend")), + new Person(new Name("Marlene Koh"), new Phone("92624417"), new Birthday("02/07/1997"), + new LevelOfFriendship("10"), new UnitNumber("#02-222"), getCcaSet("Pool"), + new Meet("17/07/2018"), getTagSet("closefriend")), + new Person(new Name("Johnny Depp"), new Phone("92624417"), new Birthday("02/12/1994"), + new LevelOfFriendship("2"), new UnitNumber("#01-346"), getCcaSet("Pool"), + new Meet("17/08/2018"), getTagSet("malafriend")), + new Person(new Name("Aditya"), new Phone("92624417"), new Birthday("02/04/1998"), + new LevelOfFriendship("3"), new UnitNumber("#6-43"), getCcaSet(), + new Meet("17/09/2018"), getTagSet("malafriend")), + new Person(new Name("Fuad"), new Phone("92624417"), new Birthday("20/04/1995"), + new LevelOfFriendship("9"), new UnitNumber("#6-43"), getCcaSet("Floorball"), + new Meet("17/04/2018"), getTagSet("colleagues")) + }; + } + + public static ReadOnlyAddressBook getSampleAddressBook() { + try { + AddressBook sampleAb = new AddressBook(); + for (Person samplePerson : getSamplePersons()) { + sampleAb.addPerson(samplePerson); + } + return sampleAb; + } catch (DuplicatePersonException e) { + throw new AssertionError("sample data cannot contain duplicate persons", e); + } + } + + /** + * Returns a cca set containing the list of strings given. + */ + public static Set getCcaSet(String... strings) { + HashSet ccas = new HashSet<>(); + for (String s : strings) { + ccas.add(new Cca(s)); + } + + return ccas; + } + + /** + * Returns a tag set containing the list of strings given. + */ + public static Set getTagSet(String... strings) { + HashSet tags = new HashSet<>(); + for (String s : strings) { + tags.add(new Tag(s)); + } + + return tags; + } + +} +``` +###### \java\seedu\address\model\util\SampleGoalDataUtil.java +``` java +/** + * Contains utility methods for populating {@code CollegeZone} with sample data. + */ +public class SampleGoalDataUtil { + + public static final EndDateTime EMPTY_END_DATE_TIME = new EndDateTime(""); + + public static Goal[] getSampleGoals() { + return new Goal[] { + + new Goal(new Importance("1"), new GoalText("finish cs2103"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("2"), new GoalText("no"), + new StartDateTime(getLocalDateTimeFromString("2018-04-08 12:12")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("3"), new GoalText("grow taller"), + new StartDateTime(getLocalDateTimeFromString("1997-04-08 12:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("3"), new GoalText("finish cs2105 assignments"), + new StartDateTime(getLocalDateTimeFromString("2018-04-08 10:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("1"), new GoalText("learning digital art"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:39")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("2"), new GoalText("finish cs2103!!!!"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("10"), new GoalText("finish cs2103!!!!"), + new StartDateTime(getLocalDateTimeFromString("2018-03-18 08:30")), + new EndDateTime("03/04/2018 12:30"), new Completion(true)), + new Goal(new Importance("6"), new GoalText("lose 0.5kg by this week"), + new StartDateTime(getLocalDateTimeFromString("2018-04-06 19:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("10"), new GoalText("Find love <3"), + new StartDateTime(getLocalDateTimeFromString("2014-04-08 20:30")), + new EndDateTime("02/02/2018 12:30"), new Completion(true)), + new Goal(new Importance("7"), new GoalText("water plants"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("5"), new GoalText("buy dog food"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("4"), new GoalText("Take the stairs more often!"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + new EndDateTime("04/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("10"), new GoalText("Eat PGP MALA once every week"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + new EndDateTime("07/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("7"), new GoalText("Make more friends in uni"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:45")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("9"), new GoalText("Go CCA regularly"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 13:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("9"), new GoalText("Drink 8 cups of water everyday"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 01:59")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("8"), new GoalText("Get A for CS2105"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 02:30")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("8"), new GoalText("Get A- for GEH1036"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 03:30")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("10"), new GoalText("Aim to increase CAP by 0.2 by the end of this semester"), + new StartDateTime(getLocalDateTimeFromString("2017-02-18 12:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("4"), new GoalText("Do 50 squats EVERYDAY"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + }; + } + + public static ReadOnlyAddressBook getSampleGoalAddressBook() { + try { + AddressBook sampleAb = new AddressBook(); + for (Goal sampleGoal : getSampleGoals()) { + sampleAb.addGoal(sampleGoal); + } + return sampleAb; + } catch (DuplicateGoalException e) { + throw new AssertionError("sample data cannot contain duplicate goals", e); + } + } +} +``` +###### \java\seedu\address\storage\XmlAdaptedCca.java +``` java +/** + * JAXB-friendly adapted version of the Cca. + */ +public class XmlAdaptedCca { + + @XmlValue + private String ccaName; + + /** + * Constructs an XmlAdaptedCca. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedCca() {} + + /** + * Constructs a {@code XmlAdaptedCca} with the given {@code ccaName}. + */ + public XmlAdaptedCca(String ccaName) { + this.ccaName = ccaName; + } + + /** + * Converts a given Tag into this class for JAXB use. + * + * @param source future changes to this will not affect the created + */ + public XmlAdaptedCca(Cca source) { + ccaName = source.ccaName; + } + + /** + * Converts this jaxb-friendly adapted cca object into the model's Cca object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person + */ + public Cca toModelType() throws IllegalValueException { + if (!Cca.isValidCcaName(ccaName)) { + throw new IllegalValueException(Cca.MESSAGE_CCA_CONSTRAINTS); + } + return new Cca(ccaName); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedCca)) { + return false; + } + + return ccaName.equals(((XmlAdaptedCca) other).ccaName); + } +} +``` +###### \java\seedu\address\storage\XmlAdaptedGoal.java +``` java +/** + * JAXB-friendly version of the Goal. + */ +public class XmlAdaptedGoal { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Goal's %s field is missing!"; + + @XmlElement(required = true) + private String importance; + @XmlElement(required = true) + private String goalText; + @XmlElement(required = true) + private String startDateTime; + @XmlElement(required = true) + private String endDateTime; + @XmlElement(required = true) + private String completion; + + /** + * Constructs an XmlAdaptedGoal. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedGoal() {} + + /** + * Constructs an {@code XmlAdaptedGoal} with the given goal details. + */ + public XmlAdaptedGoal(String importance, String goalText, String startDateTime, String endDateTime, + String completion) { + this.importance = importance; + this.goalText = goalText; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.completion = completion; + } + + /** + * Converts a given Goal into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedGoal + */ + public XmlAdaptedGoal(Goal source) { + importance = source.getImportance().value; + goalText = source.getGoalText().value; + startDateTime = source.getStartDateTime().value; + endDateTime = source.getEndDateTime().value; + completion = source.getCompletion().value; + } + + /** + * Converts this jaxb-friendly adapted goal object into the model's Goal object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted goal + */ + public Goal toModelType() throws IllegalValueException { + + if (this.importance == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Importance.class.getSimpleName())); + } + if (!Importance.isValidImportance(this.importance)) { + throw new IllegalValueException(Importance.MESSAGE_IMPORTANCE_CONSTRAINTS); + } + final Importance importance = new Importance(this.importance); + + if (this.goalText == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + GoalText.class.getSimpleName())); + } + if (!GoalText.isValidGoalText(this.goalText)) { + throw new IllegalValueException(GoalText.MESSAGE_GOAL_TEXT_CONSTRAINTS); + } + final GoalText goalText = new GoalText(this.goalText); + + if (this.startDateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + StartDateTime.class.getSimpleName())); + } + + final StartDateTime startDateTime = new StartDateTime(this.startDateTime); + + if (this.endDateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, EndDateTime + .class.getSimpleName())); + } + + final EndDateTime endDateTime = new EndDateTime(this.endDateTime); + + if (this.completion == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Completion.class.getSimpleName())); + } + + final Completion completion = new Completion((this.completion.equals("true"))); + return new Goal(importance, goalText, startDateTime, endDateTime, completion); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedGoal)) { + return false; + } + + XmlAdaptedGoal otherGoal = (XmlAdaptedGoal) other; + return Objects.equals(importance, otherGoal.importance) + && Objects.equals(goalText, otherGoal.goalText) + && Objects.equals(startDateTime, otherGoal.startDateTime) + && Objects.equals(endDateTime, otherGoal.endDateTime) + && Objects.equals(completion, otherGoal.completion); + } +} + +``` +###### \java\seedu\address\storage\XmlAdaptedPerson.java +``` java + final List personCcas = new ArrayList<>(); + for (XmlAdaptedCca cca : ccas) { + personCcas.add(cca.toModelType()); + } +``` +###### \java\seedu\address\storage\XmlAdaptedPerson.java +``` java + if (this.birthday == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Birthday.class.getSimpleName())); + } + if (!Birthday.isValidBirthday(this.birthday)) { + throw new IllegalValueException(Birthday.MESSAGE_BIRTHDAY_CONSTRAINTS); + } + final Birthday birthday = new Birthday(this.birthday); + + if (this.levelOfFriendship == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, LevelOfFriendship + .class.getSimpleName())); + } + if (!LevelOfFriendship.isValidLevelOfFriendship(this.levelOfFriendship)) { + throw new IllegalValueException(LevelOfFriendship.MESSAGE_LEVEL_OF_FRIENDSHIP_CONSTRAINTS); + } + final LevelOfFriendship levelOfFriendship = new LevelOfFriendship(this.levelOfFriendship); + +``` +###### \java\seedu\address\storage\XmlAdaptedPerson.java +``` java + if (this.unitNumber == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + UnitNumber.class.getSimpleName())); + } + if (!UnitNumber.isValidUnitNumber(this.unitNumber)) { + throw new IllegalValueException(UnitNumber.MESSAGE_UNIT_NUMBER_CONSTRAINTS); + } + final UnitNumber unitNumber = new UnitNumber(this.unitNumber); + final Set ccas = new HashSet<>(personCcas); + +``` +###### \java\seedu\address\ui\GoalCard.java +``` java +/** + * An UI component that displays information of a {@code Goal}. + */ +public class GoalCard extends UiPart { + private static final int NOT_COMPLETED_COLOUR_STYLE = 0; + private static final int COMPLETED_COLOUR_STYLE = 1; + private static final String FXML = "GoalListCard.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. + */ + + public final Goal goal; + + @FXML + private HBox goalCardPane; + @FXML + private Label goalText; + @FXML + private Label id; + @FXML + private FlowPane importance; + @FXML + private Label startDateTime; + @FXML + private Label endDateTime; + @FXML + private FlowPane completion; + + + public GoalCard(Goal goal, int displayedIndex) { + super(FXML); + this.goal = goal; + id.setText(displayedIndex + ". "); + goalText.setText(goal.getGoalText().value); + initImportance(goal); + startDateTime.setText("Start " + goal.getStartDateTime().value); + if (goal.getEndDateTime().value.equals("")) { + endDateTime.setText(goal.getEndDateTime().value); + } else { + endDateTime.setText("End " + goal.getEndDateTime().value); + } + initCompletion(goal); + } + + /** + * Creates the completion label for {@code goal}. + */ + private void initCompletion(Goal goal) { + String trueOrFalseString = goal.getCompletion().value; + if (trueOrFalseString.equals("true")) { + Image completedImage = AppUtil.getImage("/images/completedImage.png"); + ImageView completedImageView = new ImageView(completedImage); + completedImageView.setFitHeight(30); + completedImageView.setFitWidth(30); + Label completionLabel = new Label("Completed", completedImageView); + completion.getChildren().add(completionLabel); + } else { + Image ongoingImage = AppUtil.getImage("/images/ongoingImage.png"); + ImageView ongoingImageView = new ImageView(ongoingImage); + ongoingImageView.setFitHeight(27); + ongoingImageView.setFitWidth(27); + Label completionLabel = new Label("Ongoing", ongoingImageView); + completion.getChildren().add(completionLabel); + } + } + + /** + * Creates the importance label for {@code goal}. + */ + private void initImportance(Goal goal) { + String starValue = changeImportanceToStar(goal.getImportance().value); + Label importanceLabel = new Label(starValue); + importanceLabel.getStyleClass().add("yellow"); + importance.getChildren().add(importanceLabel); + } + + /** + * Takes in @param value representing the importance value + * @return a number of star string. + */ + public static String changeImportanceToStar(String value) { + int intValue = Integer.parseInt(value); + String starString = ""; + for (int i = 0; i < intValue; i++) { + starString = starString + '\u2605' + " "; + } + return starString; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof GoalCard)) { + return false; + } + + // state check + GoalCard card = (GoalCard) other; + return id.getText().equals(card.id.getText()) + && goal.equals(card.goal); + } +} +``` +###### \java\seedu\address\ui\MainWindow.java +``` java + /** + * Calculation of percentage of goal completed + * @return + */ + private int calculateGoalCompletion() { + int totalGoal = logic.getFilteredGoalList().size(); + int totalGoalCompleted = 0; + String completionStatus; + for (int i = 0; i < totalGoal; i++) { + completionStatus = logic.getFilteredGoalList().get(i).getCompletion().value; + totalGoalCompleted += isCompletedGoal(completionStatus); + } + int percentageGoalCompletion = (int) (((float) totalGoalCompleted / totalGoal) * PERCENTAGE_KEY_NUMBER); + return percentageGoalCompletion; + } + + /** + * @param completionStatus gives a String that should be either "true" or "false", indicating if the goal is + * completed. + * @return true or false + */ + private int isCompletedGoal(String completionStatus) { + int valueToAdd; + if (completionStatus.equals("true")) { + valueToAdd = 1; + } else { + valueToAdd = 0; + } + return valueToAdd; + } + + private void setThemeColour() { + setThemeColour(DEFAULT_THEME_COLOUR); + } + + private void setThemeColour(String themeColour) { + primaryStage.getScene().getStylesheets().add(EXTENSIONS_STYLESHEET); + primaryStage.getScene().getStylesheets().add(themeHashMap.get(themeColour)); + } + + private void changeThemeColour() { + primaryStage.getScene().getStylesheets().clear(); + setThemeColour(themeColour); + } + + @Subscribe + private void handleChangeThemeEvent(ThemeSwitchRequestEvent event) { + themeColour = event.themeToChangeTo; + Platform.runLater( + this::changeThemeColour + ); + } +} +``` +###### \java\seedu\address\ui\PersonCard.java +``` java + /** + * Returns the color style for {@code tagName}'s label. + */ + private String getTagColorStyleFor(String tagName) { + // we use the hash code of the tag name to generate a random color, so that the color remain consistent + // between diffe 11rent runs of the program while still making it random enough between tags. + return TAG_COLOR_STYLES[Math.abs(tagName.hashCode()) % TAG_COLOR_STYLES.length]; + } + + /** + * Creates the tag labels for {@code person}. + */ + private void initTags(Person person) { + person.getTags().forEach(tag -> { + Label tagLabel = new Label(tag.tagName); + tagLabel.getStyleClass().add(getTagColorStyleFor(tag.tagName)); + tags.getChildren().add(tagLabel); + }); + } + + private String getCcasInString(Set ccas) { + String ccasValue = ""; + Iterator iterator = ccas.iterator(); + while (iterator.hasNext()) { + ccasValue = ccasValue + iterator.next().toString() + " "; + } + return ccasValue; + } + + /** + * Takes in @param value representing the level of friendship value + * @return a number of hearts string. + */ + public static String changeLevelOfFriendshipToHeart(String value) { + int intValue = Integer.parseInt(value); + String heartString = ""; + for (int i = 0; i < intValue; i++) { + heartString = heartString + '\u2665' + " "; + } + return heartString; + } + +``` +###### \java\seedu\address\ui\PersonListPanel.java +``` java + @Subscribe + private void handleJumpToGoalListRequestEvent(JumpToListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollToGoal(event.targetIndex); + } + + /** + * Scrolls to the {@code GoalCard} at the {@code index} and selects it. + */ + private void scrollToGoal(int index) { + Platform.runLater(() -> { + goalListView.scrollTo(index); + goalListView.getSelectionModel().clearAndSelect(index); + }); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code GoalCard}. + */ + class GoalListViewCell extends ListCell { + + @Override + protected void updateItem(GoalCard goal, boolean empty) { + super.updateItem(goal, empty); + + if (empty || goal == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(goal.getRoot()); + } + } + } +} +``` +###### \java\seedu\address\ui\StatusBarFooter.java +``` java + private void setGoalCompletion(int goalCompletion) { + Platform.runLater(() -> this.goalCompletionStatus.setText("Goal " + goalCompletion + "% completed.")); + } + + public int getGoalCompletion(ObservableList goalList) { + int totalGoal = goalList.size(); + int totalGoalCompleted = 0; + String completionStatus; + for (int i = 0; i < totalGoal; i++) { + completionStatus = goalList.get(i).getCompletion().value; + totalGoalCompleted += isCompletedGoal(completionStatus); + } + int percentageGoalCompletion = (int) (((float) totalGoalCompleted / totalGoal) * PERCENTAGE_KEY_NUMBER); + return percentageGoalCompletion; + } + + /** + * @param completionStatus gives a String that should be either "true" or "false", indicating if the goal is + * completed. + * @return 1 or 0 + */ + private int isCompletedGoal(String completionStatus) { + int valueToAdd; + if (completionStatus.equals("true")) { + valueToAdd = 1; + } else { + valueToAdd = 0; + } + return valueToAdd; + } +``` +###### \resources\view\BubblegumTheme.css +``` css +.background { + -fx-background-color: derive(#ffdae0, 20%); + background-color: #ffb6c1; +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Lato"; + -fx-text-fill: #FFFF99; + -fx-opacity: 0.9; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Lato"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Lato Light"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Lato"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #ffdae0; + -fx-control-inner-background: #ffdae0; + -fx-background-color: #ffdae0; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Lato"; + -fx-text-fill: black; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: derive(#ffdae0, 20%); + -fx-border-color: transparent transparent transparent #4d4d4d; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: derive(#ffdae0, 20%); +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: derive(#ffdae0, 20%); +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:filled:even { + -fx-background-color: #BFEFFF; +} + +.list-cell:filled:odd { + -fx-background-color: #63D1F4; +} + +.list-cell:filled:selected { + -fx-background-color: # 00BFFF; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #3e7b91; + -fx-border-width: 1; +} + +.list-cell .label { + -fx-text-fill: black; +} + +.cell_big_label { + -fx-font-family: "Lato"; + -fx-font-size: 18px; + -fx-text-fill: #010504; +} + +.cell_small_label { + -fx-font-family: "Lato"; + -fx-font-size: 13px; + -fx-text-fill: #010504; +} + +.anchor-pane { + -fx-background-color: derive(#ffdae0, 20%); +} + +.pane-with-border { + -fx-background-color: derive(#ffdae0, 20%); + -fx-border-color: derive(#ffdae0, 10%); + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: derive(#ffdae0, 20%); + -fx-text-fill: black; +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Lato Light"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +.result-display .label { + -fx-text-fill: black !important; +} + +.status-bar .label { + -fx-font-family: "Lato"; + -fx-text-fill: black; +} + +.status-bar-with-border { + -fx-background-color: derive(#ffdae0, 30%); + -fx-border-color: derive(#ffdae0, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: black; +} + +.grid-pane { + -fx-background-color: derive(#ffdae0, 30%); + -fx-border-color: derive(#ffdae0, 30%); + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: derive(#ffdae0, 30%); +} + +.context-menu { + -fx-background-color: derive(#ffdae0, 50%); +} + +.context-menu .label { + -fx-text-fill: black; +} + +.menu-bar { + -fx-background-color: derive(#ffdae0, 20%); +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Lato Light"; + -fx-text-fill: black; + -fx-opacity: 0.9; +} +/*a*/ +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #313131; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #ffdae0; + -fx-font-family: "Lato"; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #ebebeb; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: black; + -fx-text-fill: #ffdae0; +} + +.button:focused { + -fx-border-color: black, black; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #ffdae0; + -fx-text-fill: black; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffdae0; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: #ffdae0; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #ffdae0; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: black; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#ffdae0, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: black; + -fx-text-fill: black; +} + +.scroll-bar { + -fx-background-color: derive(#BA55D3, 20%); +} + +.scroll-bar .thumb { + -fx-background-color: derive(#EE82EE, 50%); + -fx-background-insets: 3; +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-padding: 0 0 0 0; +} + +.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { + -fx-shape: " "; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 1 8 1 8; +} + +.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { + -fx-padding: 8 1 8 1; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-width: 0; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#commandTextField { + -fx-background-color: transparent #ffb6c1 transparent #ffb6c1; + -fx-background-insets: 0; + -fx-border-color: #ffb6c1 #ffb6c1 #FF7F50 #ffb6c1; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Lato Light"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, white, 10, 0, 0, 0); +} + +#resultDisplay .content { + -fx-background-color: transparent, #ffb6c1, transparent, #ffb6c1; + -fx-background-radius: 0; +} + +#tags { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#tags .label { + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} + + +#tags .teal { + -fx-text-fill: white; + -fx-background-color: #3e7b91; +} + +#tags .red { + -fx-text-fill: black; + -fx-background-color: #ff7675; +} + +#tags .yellow { + -fx-background-color: #ffeaa7; + -fx-text-fill: black; +} + +#tags .blue { + -fx-text-fill: black; + -fx-background-color: #48dbfb; +} + +#tags .orange { + -fx-text-fill: black; + -fx-background-color: #ffa502; +} + +#tags .brown { + -fx-text-fill: black; + -fx-background-color: #D7ACAC; +} + +#tags .green { + -fx-text-fill: black; + -fx-background-color: #55efc4; +} + +#tags .pink { + -fx-text-fill: black; + -fx-background-color: #fd79a8; +} + +#tags .black { + -fx-text-fill: white; + -fx-background-color: black; +} + +#tags .purple { + -fx-text-fill: black; + -fx-background-color: #a29bfe; +} + +#importance { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#importance .label { + -fx-background-color: #FFE761; + -fx-text-fill: black; + -fx-padding: 1 3 1 3; + -fx-border-radius: 3; + -fx-background-radius: 2; + -fx-font-size: 13; +} +``` +###### \resources\view\GoalListCard.fxml +``` fxml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` +###### \resources\view\LightTheme.css +``` css +.background { + -fx-background-color: derive(#ffffff, 20%); + background-color: #f5f5f5; +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Lato"; + -fx-text-fill: #cecece; + -fx-opacity: 0.9; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Lato"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Lato Light"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Lato"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #ffffff; + -fx-control-inner-background: #ffffff; + -fx-background-color: #ffffff; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Lato"; + -fx-text-fill: black; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: derive(#ffffff, 20%); + -fx-border-color: transparent transparent transparent #4d4d4d; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: derive(#ffffff, 20%); +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: derive(#ffffff, 20%); +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:filled:even { + -fx-background-color: #ebebeb; +} + +.list-cell:filled:odd { + -fx-background-color: #fcf9f9; +} + +.list-cell:filled:selected { + -fx-background-color: #dae3f2; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #3e7b91; + -fx-border-width: 1; +} + +.list-cell .label { + -fx-text-fill: black; +} + +.cell_big_label { + -fx-font-family: "Lato"; + -fx-font-size: 18px; + -fx-text-fill: #010504; +} + +.cell_small_label { + -fx-font-family: "Lato"; + -fx-font-size: 13px; + -fx-text-fill: #010504; +} + +.anchor-pane { + -fx-background-color: derive(#ffffff, 20%); +} + +.pane-with-border { + -fx-background-color: derive(#ffffff, 20%); + -fx-border-color: derive(#ffffff, 10%); + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: derive(#ffffff, 20%); + -fx-text-fill: black; +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Lato Light"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +.result-display .label { + -fx-text-fill: black !important; +} + +.status-bar .label { + -fx-font-family: "Lato"; + -fx-text-fill: black; +} + +.status-bar-with-border { + -fx-background-color: derive(#ffffff, 30%); + -fx-border-color: derive(#ffffff, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: black; +} + +.grid-pane { + -fx-background-color: derive(#ffffff, 30%); + -fx-border-color: derive(#ffffff, 30%); + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: derive(#ffffff, 30%); +} + +.context-menu { + -fx-background-color: derive(#ffffff, 50%); +} + +.context-menu .label { + -fx-text-fill: black; +} + +.menu-bar { + -fx-background-color: derive(#ffffff, 20%); +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Lato Light"; + -fx-text-fill: black; + -fx-opacity: 0.9; +} +/*a*/ +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #313131; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #ffffff; + -fx-font-family: "Lato"; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #ebebeb; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: black; + -fx-text-fill: #ffffff; +} + +.button:focused { + -fx-border-color: black, black; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #ffffff; + -fx-text-fill: black; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: black; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#ffffff, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: black; + -fx-text-fill: black; +} + +.scroll-bar { + -fx-background-color: derive(grey, 20%); +} + +.scroll-bar .thumb { + -fx-background-color: derive(#ffffff, 50%); + -fx-background-insets: 3; +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-padding: 0 0 0 0; +} + +.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { + -fx-shape: " "; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 1 8 1 8; +} + +.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { + -fx-padding: 8 1 8 1; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-width: 0; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#commandTextField { + -fx-background-color: transparent #f5f5f5 transparent #f5f5f5; + -fx-background-insets: 0; + -fx-border-color: #ffffff #ffffff #383838 #ffffff; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Lato Light"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, white, 10, 0, 0, 0); +} + +#resultDisplay .content { + -fx-background-color: transparent, #f5f5f5, transparent, #f5f5f5; + -fx-background-radius: 0; +} + +#tags { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#tags .label { + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} + + +#tags .teal { + -fx-text-fill: white; + -fx-background-color: #3e7b91; +} + +#tags .red { + -fx-text-fill: black; + -fx-background-color: #ff7675; +} + +#tags .yellow { + -fx-background-color: #ffeaa7; + -fx-text-fill: black; +} + +#tags .blue { + -fx-text-fill: black; + -fx-background-color: #48dbfb; +} + +#tags .orange { + -fx-text-fill: black; + -fx-background-color: #ffa502; +} + +#tags .brown { + -fx-text-fill: black; + -fx-background-color: #D7ACAC; +} + +#tags .green { + -fx-text-fill: black; + -fx-background-color: #55efc4; +} + +#tags .pink { + -fx-text-fill: black; + -fx-background-color: #fd79a8; +} + +#tags .black { + -fx-text-fill: white; + -fx-background-color: black; +} + +#tags .purple { + -fx-text-fill: black; + -fx-background-color: #a29bfe; +} + +#importance { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#importance .label { + -fx-background-color: #FFE761; + -fx-text-fill: black; + -fx-padding: 1 3 1 3; + -fx-border-radius: 3; + -fx-background-radius: 2; + -fx-font-size: 13; +} +``` diff --git a/collated/functional/fuadsahmawi.md b/collated/functional/fuadsahmawi.md new file mode 100644 index 000000000000..b802ea88ab04 --- /dev/null +++ b/collated/functional/fuadsahmawi.md @@ -0,0 +1,1121 @@ +# fuadsahmawi +###### \java\seedu\address\logic\commands\AddReminderCommand.java +``` java +/** + * Adds a reminder to the Calendar. + */ +public class AddReminderCommand extends UndoableCommand { + public static final String COMMAND_WORD = "+reminder"; + public static final String COMMAND_ALIAS = "+r"; + public static final String COMMAND_ALIAS_2 = "addreminder"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a reminder to Calendar.\n" + + "Parameters: " + + PREFIX_REMINDER_TEXT + "TEXT " + + PREFIX_DATE + "START_DATETIME " + + PREFIX_END_DATE + "END_DATETIME\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_REMINDER_TEXT + " do homework " + + PREFIX_DATE + " tonight 8pm " + + PREFIX_END_DATE + " tonight 10pm"; + + public static final String MESSAGE_SUCCESS = "New reminder added: %1$s\n" + + "Disclaimer: If date & time parsed wrongly, delete the reminder and refer to User Guide for correct" + + " format of date and time"; + + public static final String MESSAGE_DUPLICATE_REMINDER = "This reminder already exists in the Calendar"; + + private final Reminder toAdd; + + /** + * Creates an AddReminderCommand to add the specified {@code Reminder} + */ + public AddReminderCommand(Reminder reminder) { + requireNonNull(reminder); + toAdd = reminder; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + model.addReminder(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicateReminderException e) { + throw new CommandException(MESSAGE_DUPLICATE_REMINDER); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddReminderCommand // instanceof handles nulls + && toAdd.equals(((AddReminderCommand) other).toAdd)); + } +} +``` +###### \java\seedu\address\logic\commands\DeleteReminderCommand.java +``` java +/** + * Deletes a reminder identified using its title in the calendar + */ +public class DeleteReminderCommand extends UndoableCommand { + public static final String COMMAND_WORD = "-reminder"; + public static final String COMMAND_ALIAS = "-r"; + public static final String COMMAND_ALIAS_2 = "deletereminder"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the reminder identified by its title & start time in the calendar.\n" + + "Parameters: REMINDER_TITLE & START_DATETIME\n" + + "Example: " + COMMAND_WORD + " text/Eat pills d/tmr 8pm"; + + public static final String MESSAGE_DELETE_REMINDER_SUCCESS = "Deleted Reminder: %1$s"; + + private Index targetIndex; + + private String dateTime; + + private ReminderTextPredicate predicate; + + private Reminder reminderToDelete; + + public DeleteReminderCommand(ReminderTextPredicate predicate, String dateTime) { + this.predicate = predicate; + this.dateTime = dateTime; + } + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(reminderToDelete); + try { + model.deleteReminder(reminderToDelete); + } catch (ReminderNotFoundException pnfe) { + throw new AssertionError("The target reminder cannot be missing"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_REMINDER_SUCCESS, reminderToDelete)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + model.updateFilteredReminderList(predicate); + List lastShownList = model.getFilteredReminderList(); + targetIndex = Index.fromOneBased(1); + if (lastShownList.size() > 1) { + for (Reminder reminder : lastShownList) { + if (reminder.getDateTime().toString().equals(dateTime)) { + reminderToDelete = reminder; + } + } + } else { + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_REMINDER_TEXT_DATE); + } + + reminderToDelete = lastShownList.get(targetIndex.getZeroBased()); + } + } +} +``` +###### \java\seedu\address\logic\commands\FindCommand.java +``` java +/** + * Finds and lists all persons in CollegeZone whose name contains any of the argument keywords. + * Keyword matching is case sensitive. + */ +public class FindCommand extends Command { + + public static final String COMMAND_WORD = "find"; + + public static final String COMMAND_ALIAS = "f"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " 1" + + PREFIX_NAME + " " + + PREFIX_PHONE + " " + + PREFIX_BIRTHDAY + " " + + PREFIX_LEVEL_OF_FRIENDSHIP + " " + + PREFIX_UNIT_NUMBER + " " + + PREFIX_CCA + " " + + PREFIX_TAG + " "; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names or tags contain any of " + + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" + + "Parameters: n/KEYWORD [MORE_KEYWORDS]... or t/KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " n/alice bob charlie"; + + public static final String MESSAGE_NOT_EDITED = "A keyword to find name or tag must be provided."; + + private TagContainsKeywordsPredicate predicateT = null; + private NameContainsKeywordsPredicate predicateN = null; + + public FindCommand(NameContainsKeywordsPredicate predicateName) { + this.predicateN = predicateName; + } + + public FindCommand(TagContainsKeywordsPredicate predicate) { + this.predicateT = predicate; + } + + @Override + public CommandResult execute() { + if (predicateT == null) { + model.updateFilteredPersonList(predicateN); + } else { + model.updateFilteredPersonList(predicateT); + } + return new CommandResult(getMessageForPersonListShownSummary(model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + if (this.predicateT == null) { + return other == this // short circuit if same object + || (other instanceof FindCommand // instanceof handles nulls + && this.predicateN.equals(((FindCommand) other).predicateN)); // state check + } else { + return other == this // short circuit if same object + || (other instanceof FindCommand // instanceof handles nulls + && this.predicateT.equals(((FindCommand) other).predicateT)); // state check + } + } + + /** + * Stores the details to edit the person with. Each non-empty field value will replace the + * corresponding field value of the person. + */ + public static class FindPersonDescriptor { + private String[] nameKeywords; + private String[] tagKeywords; + + public FindPersonDescriptor() { + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(this.nameKeywords, this.tagKeywords); + } + + public void setNameKeywords(String name) { + this.nameKeywords = name.split("\\s+"); + ; + } + + public void setTagKeywords(String tags) { + this.tagKeywords = tags.split("\\s+"); + } + + public String[] getNameKeywords() { + return this.nameKeywords; + } + + public String[] getTagKeyWords() { + return this.tagKeywords; + } + } +} +``` +###### \java\seedu\address\logic\parser\AddReminderCommandParser.java +``` java +/** + * Parses input arguments and creates a new AddReminderCommand object + */ +public class AddReminderCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddReminderCommand + * and returns an AddReminderCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddReminderCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_REMINDER_TEXT, PREFIX_DATE, PREFIX_END_DATE); + + if (!arePrefixesPresent(argMultimap, PREFIX_REMINDER_TEXT, PREFIX_DATE, PREFIX_END_DATE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddReminderCommand.MESSAGE_USAGE)); + } + + if (nattyDateAndTimeParser(argMultimap.getValue(PREFIX_DATE).get()).get().compareTo( + nattyDateAndTimeParser(argMultimap.getValue(PREFIX_END_DATE).get()).get()) > 0 + || nattyDateAndTimeParser(argMultimap.getValue(PREFIX_END_DATE).get()).get().compareTo( + LocalDateTime.now()) < 0 + || nattyDateAndTimeParser(argMultimap.getValue(PREFIX_DATE).get()).get().compareTo( + LocalDateTime.now()) < 0) { + throw new ParseException(String.format(MESSAGE_INVALID_DATE_FORMAT, AddReminderCommand.MESSAGE_USAGE)); + } + + try { + ReminderText reminderText = ParserUtil.parseReminderText(argMultimap.getValue(PREFIX_REMINDER_TEXT)).get(); + DateTime dateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATE)).get(); + EndDateTime endDateTime = ParserUtil.parseEndDateTime(argMultimap.getValue(PREFIX_END_DATE)).get(); + Reminder reminder = new Reminder(reminderText, dateTime, endDateTime); + return new AddReminderCommand(reminder); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} +``` +###### \java\seedu\address\logic\parser\DeleteReminderCommandParser.java +``` java +/** + * Parses input arguments and creates a new DeleteReminderCommand object + */ +public class DeleteReminderCommandParser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteReminderCommand + * and returns an DeleteReminderCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteReminderCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_REMINDER_TEXT, PREFIX_DATE); + + if (!arePrefixesPresent(argMultimap, PREFIX_REMINDER_TEXT, PREFIX_DATE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteReminderCommand.MESSAGE_USAGE)); + } + + String reminderText = argMultimap.getValue(PREFIX_REMINDER_TEXT).get(); + String dateTime = argMultimap.getValue(PREFIX_DATE).get(); + LocalDateTime localDateTime = nattyDateAndTimeParser(dateTime).get(); + dateTime = properReminderDateTimeFormat(localDateTime); + String trimmedArgs = reminderText.trim(); + + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteReminderCommand.MESSAGE_USAGE)); + } + + String[] nameKeywords = trimmedArgs.split("\\s+"); + + return new DeleteReminderCommand(new ReminderTextPredicate(Arrays.asList(nameKeywords)), dateTime); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} +``` +###### \java\seedu\address\logic\parser\FindCommandParser.java +``` java +/** + * Parses input arguments and creates a new FindCommand object + */ +public class FindCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindCommand + * and returns an FindCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_TAG); + + FindPersonDescriptor findPersonDescriptor = new FindPersonDescriptor(); + + argMultimap.getValue(PREFIX_NAME).ifPresent(findPersonDescriptor::setNameKeywords); + argMultimap.getValue(PREFIX_TAG).ifPresent(findPersonDescriptor::setTagKeywords); + + + if (!findPersonDescriptor.isAnyFieldEdited()) { + throw new ParseException(FindCommand.MESSAGE_NOT_EDITED); + } + + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + return new FindCommand( + new NameContainsKeywordsPredicate(Arrays.asList(findPersonDescriptor.getNameKeywords()))); + } else { + return new FindCommand( + new TagContainsKeywordsPredicate(Arrays.asList(findPersonDescriptor.getTagKeyWords()))); + } + } +} +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + + /** + * Parses a {@code String reminderText} into an {@code ReminderText}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code reminderText} is invalid. + */ + public static ReminderText parseReminderText(String reminderText) throws IllegalValueException { + requireNonNull(reminderText); + String trimmedReminderText = reminderText.trim(); + if (!ReminderText.isValidReminderText(trimmedReminderText)) { + throw new IllegalValueException(ReminderText.MESSAGE_REMINDER_TEXT_CONSTRAINTS); + } + return new ReminderText(trimmedReminderText); + } + + /** + * Parses a {@code Optional reminderText} into an {@code Optional} if {@code reminderText} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseReminderText(Optional reminderText) throws IllegalValueException { + requireNonNull(reminderText); + return reminderText.isPresent() ? Optional.of(parseReminderText(reminderText.get())) : Optional.empty(); + } + + /** + * Parses a {@code String reminderText} into an {@code ReminderText}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code reminderText} is invalid. + */ + public static DateTime parseDateTime(String dateTime) throws IllegalValueException { + requireNonNull(dateTime); + String trimmedDateTime = dateTime.trim(); + if (!DateTime.isValidDateTime(trimmedDateTime)) { + throw new IllegalValueException(DateTime.MESSAGE_DATE_TIME_CONSTRAINTS); + } + return new DateTime(trimmedDateTime); + } + + /** + * Parses a {@code Optional reminderText} into an {@code Optional} if {@code reminderText} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseDateTime(Optional dateTime) throws IllegalValueException { + requireNonNull(dateTime); + return dateTime.isPresent() ? Optional.of(parseDateTime(dateTime.get())) : Optional.empty(); + } + + /** + * Parses a {@code String reminderText} into an {@code ReminderText}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code reminderText} is invalid. + */ + public static EndDateTime parseEndDateTime(String endDateTime) throws IllegalValueException { + requireNonNull(endDateTime); + String trimmedEndDateTime = endDateTime.trim(); + if (!DateTime.isValidDateTime(trimmedEndDateTime)) { + throw new IllegalValueException(EndDateTime.MESSAGE_END_DATE_TIME_CONSTRAINTS); + } + return new EndDateTime(trimmedEndDateTime); + } + + /** + * Parses a {@code Optional reminderText} into an {@code Optional} if {@code reminderText} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseEndDateTime(Optional endDateTime) throws IllegalValueException { + requireNonNull(endDateTime); + return endDateTime.isPresent() ? Optional.of(parseEndDateTime(endDateTime.get())) : Optional.empty(); + } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Adds a reminder to CollegeZone. + * @throws DuplicateReminderException if an equivalent reminder already exists. + */ + public void addReminder (Reminder r) throws DuplicateReminderException { + reminders.add(r); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * @throws ReminderNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removeReminder(Reminder key) throws ReminderNotFoundException { + if (reminders.remove(key)) { + return true; + } else { + throw new ReminderNotFoundException(); + } + } + /** + * Replaces the given reminder {@code target} in the list with {@code editedReminder}. + * + * @throws DuplicateReminderException if updating the reminder's details causes the reminder to be equivalent to + * another existing reminder in the list. + * @throws ReminderNotFoundException if {@code target} could not be found in the list. + */ + public void updateReminder(Reminder target, Reminder editedReminder) + throws DuplicateReminderException, ReminderNotFoundException { + requireNonNull(editedReminder); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any person + // in the person list. + reminders.setReminder(target, editedReminder); + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public void addReminder(Reminder reminder) throws DuplicateReminderException { + addressBook.addReminder(reminder); + updateFilteredReminderList(PREDICATE_SHOW_ALL_REMINDERS); + indicateAddressBookChanged(); + } + + @Override + public ObservableList getFilteredReminderList() { + return FXCollections.unmodifiableObservableList(filteredReminders); + } + + @Override + public void updateFilteredReminderList(Predicate predicate) { + requireNonNull(predicate); + filteredReminders.setPredicate(predicate); + } + + @Override + public synchronized void deleteReminder(Reminder reminder) throws ReminderNotFoundException { + addressBook.removeReminder(reminder); + indicateAddressBookChanged(); + } + /* + @Override + public void updateReminder(Reminder target, Reminder editedReminder) + throws DuplicateReminderException, ReminderNotFoundException { + requireAllNonNull(target, editedReminder); + + addressBook.updateReminder(target, editedReminder); + indicateAddressBookChanged(); + } + */ +} +``` +###### \java\seedu\address\model\person\TagContainsKeywordsPredicate.java +``` java +/** + * Tests that a {@code Person}'s {@code Tags} matches any of the keywords given. + */ + +public class TagContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public TagContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + Iterator ir = person.getTags().iterator(); + StringBuilder tag = new StringBuilder(); + while (ir.hasNext()) { + tag.append(ir.next().tagName); + tag.append(" "); + } + + String tagS = tag.toString(); + + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(tagS, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TagContainsKeywordsPredicate // instanceof handles nulls + && this.keywords.equals(((TagContainsKeywordsPredicate) other).keywords)); // state check + } + +} +``` +###### \java\seedu\address\model\ReadOnlyAddressBook.java +``` java + /** + * Returns an unmodifiable view of the reminders list. + * This list will not contain any duplicate reminders. + */ + ObservableList getReminderList(); + +} +``` +###### \java\seedu\address\model\reminder\EndDateTime.java +``` java +/** + * Represents a Reminder's end date and time in the Calendar. + * Guarantees: immutable; is valid as declared in {@link #isValidEndDateTime(String)} + */ +public class EndDateTime { + + + public static final String MESSAGE_END_DATE_TIME_CONSTRAINTS = + "EndDateTime must be a valid date and time"; + public final String endDateTime; + + /** + * Constructs a {@code EndDateTime}. + * + * @param endDateTime A valid endDateTime number. + */ + public EndDateTime(String endDateTime) { + if (endDateTime.equals("")) { + this.endDateTime = ""; + } else { + checkArgument(isValidEndDateTime(endDateTime), MESSAGE_END_DATE_TIME_CONSTRAINTS); + LocalDateTime localEndDateTime = nattyDateAndTimeParser(endDateTime).get(); + this.endDateTime = properReminderDateTimeFormat(localEndDateTime); + } + } + + /** + * Returns true if a given string is a valid person endDateTime number. + */ + public static boolean isValidEndDateTime(String test) { + Optional localEndDateTime = nattyDateAndTimeParser(test); + if (localEndDateTime.isPresent()) { + return true; + } else { + return false; + } + + } + + @Override + public String toString() { + return endDateTime; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EndDateTime // instanceof handles nulls + && this.endDateTime.equals(((EndDateTime) other).endDateTime)); // state check + } + + @Override + public int hashCode() { + return endDateTime.hashCode(); + } +} +``` +###### \java\seedu\address\model\reminder\exceptions\DuplicateReminderException.java +``` java +/** + * Signals that the operation will result in duplicate Reminder objects. + */ +public class DuplicateReminderException extends DuplicateDataException { + public DuplicateReminderException() { + super("Operation will result in duplicate reminders"); + } +} +``` +###### \java\seedu\address\model\reminder\exceptions\ReminderNotFoundException.java +``` java +/** + * Signals that the operation is unable to find the specified reminder. + */ +public class ReminderNotFoundException extends Exception { +} +``` +###### \java\seedu\address\model\reminder\Reminder.java +``` java +/** + * Represents a Reminder + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Reminder { + + private final ReminderText reminderText; + private final DateTime dateTime; + private final EndDateTime endDateTime; + + /** + * Every field must be present and not null. + */ + + public Reminder(ReminderText reminderText, DateTime dateTime, EndDateTime endDateTime) { + requireAllNonNull(reminderText, dateTime); + this.reminderText = reminderText; + this.dateTime = dateTime; + this.endDateTime = endDateTime; + } + + public ReminderText getReminderText() { + return reminderText; + } + + public DateTime getDateTime() { + return dateTime; + } + + public EndDateTime getEndDateTime() { + return endDateTime; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Reminder)) { + return false; + } + + Reminder otherReminder = (Reminder) other; + return otherReminder.getReminderText().equals(this.getReminderText()) + && otherReminder.getDateTime().equals(this.getDateTime()) + && otherReminder.getEndDateTime().equals(this.getEndDateTime()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(reminderText, dateTime, endDateTime); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(" Reminder: ") + .append(getReminderText()) + .append(" Date & Time: ") + .append(getDateTime()) + .append(" End Date & Time: ") + .append(getEndDateTime()); + + return builder.toString(); + } +} +``` +###### \java\seedu\address\model\reminder\ReminderText.java +``` java +/** + * Represents a Reminder's text in the Calendar. + * Guarantees: immutable; is valid as declared in {@link #isValidReminderText(String)} + */ +public class ReminderText { + + public static final String MESSAGE_REMINDER_TEXT_CONSTRAINTS = + "Reminder text can be any expression that are not just whitespaces."; + public static final String REMINDER_TEXT_VALIDATION_REGEX = "^(?!\\s*$).+"; + public final String reminderText; + + /** + * Constructs a {@code ReminderText}. + * + * @param reminderText A valid reminder text. + */ + public ReminderText(String reminderText) { + requireNonNull(reminderText); + checkArgument(isValidGoalText(reminderText), MESSAGE_REMINDER_TEXT_CONSTRAINTS); + this.reminderText = reminderText; + } + + /** + * Returns true if a given string is a valid reminder text. + */ + public static boolean isValidReminderText(String test) { + return test.matches(REMINDER_TEXT_VALIDATION_REGEX); + } + + /** + * Returns true if a given string is a valid reminder text. + */ + public static boolean isValidGoalText(String test) { + return test.matches(REMINDER_TEXT_VALIDATION_REGEX); + } + + @Override + public String toString() { + return reminderText; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.address.model.goal.GoalText // instanceof handles nulls + && this.reminderText.equals((( + seedu.address.model.reminder.ReminderText) other).reminderText)); // state check + } + + @Override + public int hashCode() { + return reminderText.hashCode(); + } + +} +``` +###### \java\seedu\address\model\reminder\ReminderTextPredicate.java +``` java +/** + * Tests that a {@code Reminder}'s {@code ReminderText} matches any of the keywords given. + */ +public class ReminderTextPredicate implements Predicate { + private final List keywords; + + public ReminderTextPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Reminder reminder) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(reminder.getReminderText().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ReminderTextPredicate // instanceof handles nulls + && this.keywords.equals(((ReminderTextPredicate) other).keywords)); // state check + } +} +``` +###### \java\seedu\address\model\reminder\UniqueReminderList.java +``` java +/** + * A list of reminders that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Reminder#equals(Object) + */ +public class UniqueReminderList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent reminder as the given argument. + */ + public boolean contains(Reminder toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a reminder to the list. + * + * @throws DuplicateReminderException if the reminder to add is a duplicate of an existing reminder in the list. + */ + public void add(Reminder toAdd) throws DuplicateReminderException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateReminderException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the reminder {@code target} in the list with {@code editedReminder}. + * + * @throws DuplicateReminderException if the replacement is equivalent to another existing reminder in the list. + * @throws ReminderNotFoundException if {@code target} could not be found in the list. + */ + public void setReminder(Reminder target, Reminder editedReminder) + throws DuplicateReminderException, ReminderNotFoundException { + requireNonNull(editedReminder); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new ReminderNotFoundException(); + } + + if (!target.equals(editedReminder) && internalList.contains(editedReminder)) { + throw new DuplicateReminderException(); + } + + internalList.set(index, editedReminder); + } + + /** + * Removes the equivalent reminder from the list. + * + * @throws ReminderNotFoundException if no such reminder could be found in the list. + */ + public boolean remove(Reminder toRemove) throws ReminderNotFoundException { + requireNonNull(toRemove); + final boolean reminderFoundAndDeleted = internalList.remove(toRemove); + if (!reminderFoundAndDeleted) { + throw new ReminderNotFoundException(); + } + return reminderFoundAndDeleted; + } + + public void setReminders(UniqueReminderList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setReminders(List reminders) throws DuplicateReminderException { + requireAllNonNull(reminders); + final UniqueReminderList replacement = new UniqueReminderList(); + for (final Reminder reminder : reminders) { + replacement.add(reminder); + } + setReminders(replacement); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueReminderList // instanceof handles nulls + && this.internalList.equals(((UniqueReminderList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} +``` +###### \java\seedu\address\model\util\SampleReminderDataUtil.java +``` java +/** + * Contains utility methods for populating {@code CollegeZone} with sample reminder data. + */ +public class SampleReminderDataUtil { + + public static Reminder[] getSampleReminders() { + return new Reminder[] { + + new Reminder(new ReminderText("CS2103T Submission"), + new DateTime("2018-04-15 23:00"), + new EndDateTime("2018-04-15 23:59")), + new Reminder(new ReminderText("Gym Session"), + new DateTime("2018-04-13 14:00"), + new EndDateTime("2018-04-13 16:00")), + new Reminder(new ReminderText("Gym Session"), + new DateTime("2018-04-06 14:00"), + new EndDateTime("2018-04-06 16:00")), + new Reminder(new ReminderText("Gym Session"), + new DateTime("2018-04-20 14:00"), + new EndDateTime("2018-04-20 16:00")), + new Reminder(new ReminderText("Gym Session"), + new DateTime("2018-04-27 14:00"), + new EndDateTime("2018-04-27 16:00")), + new Reminder(new ReminderText("Recess Week"), + new DateTime("2018-04-23 00:00"), + new EndDateTime("2018-04-27 23:59")), + new Reminder(new ReminderText("CS2103T Software Demo"), + new DateTime("2018-04-19 09:00"), + new EndDateTime("2018-04-19 10:00")), + new Reminder(new ReminderText("CS2103T Group Meeting"), + new DateTime("2018-04-14 11:00"), + new EndDateTime("2018-04-14 18:00")), + new Reminder(new ReminderText("Chalet"), + new DateTime("2018-04-21 10:00"), + new EndDateTime("2018-04-22 20:00")), + new Reminder(new ReminderText("Medical Appointment"), + new DateTime("2018-04-05 15:00"), + new EndDateTime("2018-04-05 17:00")), + }; + } +} +``` +###### \java\seedu\address\storage\XmlAdaptedReminder.java +``` java +/** + * JAXB-friendly version of the Reminder. + */ +public class XmlAdaptedReminder { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Reminder's %s field is missing!"; + + @XmlElement(required = true) + private String reminderText; + @XmlElement(required = true) + private String dateTime; + @XmlElement(required = true) + private String endDateTime; + + /** + * Constructs an XmlAdaptedReminder. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedReminder() {} + + /** + * Constructs an {@code XmlAdaptedReminder} with the given person details. + */ + public XmlAdaptedReminder(String reminderText, String dateTime, String endDateTime) { + this.reminderText = reminderText; + this.dateTime = dateTime; + this.endDateTime = endDateTime; + } + + /** + * Converts a given Reminder into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedReminder + */ + public XmlAdaptedReminder(Reminder source) { + reminderText = source.getReminderText().toString(); + dateTime = source.getDateTime().toString(); + endDateTime = source.getEndDateTime().toString(); + } + + /** + * Converts this jaxb-friendly adapted reminder object into the model's Reminder object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted reminder + */ + public Reminder toModelType() throws IllegalValueException { + if (this.reminderText == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + ReminderText.class.getSimpleName())); + } + if (!ReminderText.isValidReminderText(this.reminderText)) { + throw new IllegalValueException(ReminderText.MESSAGE_REMINDER_TEXT_CONSTRAINTS); + } + final ReminderText reminderText = new ReminderText(this.reminderText); + + if (this.dateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + DateTime.class.getSimpleName())); + } + if (!DateTime.isValidDateTime(this.dateTime)) { + throw new IllegalValueException(DateTime.MESSAGE_DATE_TIME_CONSTRAINTS); + } + final DateTime dateTime = new DateTime(this.dateTime); + + if (this.endDateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + EndDateTime.class.getSimpleName())); + } + if (!DateTime.isValidDateTime(this.endDateTime)) { + throw new IllegalValueException(EndDateTime.MESSAGE_END_DATE_TIME_CONSTRAINTS); + } + final EndDateTime endDateTime = new EndDateTime(this.endDateTime); + + return new Reminder(reminderText, dateTime, endDateTime); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedReminder)) { + return false; + } + + XmlAdaptedReminder otherPerson = (XmlAdaptedReminder) other; + return Objects.equals(reminderText, otherPerson.reminderText) + && Objects.equals(dateTime, otherPerson.dateTime) + && Objects.equals(endDateTime, otherPerson.endDateTime); + } +} +``` +###### \java\seedu\address\ui\CalendarPanel.java +``` java +/** + * The Calendar Panel of the App. + */ +public class CalendarPanel extends UiPart { + private static final String FXML = "CalendarPanel.fxml"; + + private CalendarView calendarView; + + private ObservableList reminderList; + + private ObservableList personList; + + public CalendarPanel(ObservableList reminderList, ObservableList personList) { + super(FXML); + + this.reminderList = reminderList; + this.personList = personList; + + calendarView = new CalendarView(); + setupCalendar(); + updateCalendar(); + registerAsAnEventHandler(this); + } + + @Subscribe + private void handleNewCalendarEvent(AddressBookChangedEvent event) { + reminderList = event.data.getReminderList(); + personList = event.data.getPersonList(); + Platform.runLater(this::updateCalendar); + } + + /** + * Updates the Calendar with Reminders that are already added + */ + private void updateCalendar() { + setDateAndTime(); + CalendarSource myCalendarSource = new CalendarSource("Reminders and Meetups"); + Calendar calendarRDue = new Calendar("Reminders Already Due"); + Calendar calendarRNotDue = new Calendar("Reminders Not Due"); + Calendar calendarM = new Calendar("Meetups"); + calendarRDue.setStyle(Calendar.Style.getStyle(4)); + calendarRDue.setLookAheadDuration(Duration.ofDays(365)); + calendarRNotDue.setStyle(Calendar.Style.getStyle(1)); + calendarRNotDue.setLookAheadDuration(Duration.ofDays(365)); + calendarM.setStyle(Calendar.Style.getStyle(3)); + myCalendarSource.getCalendars().add(calendarRDue); + myCalendarSource.getCalendars().add(calendarRNotDue); + myCalendarSource.getCalendars().add(calendarM); + for (Reminder reminder : reminderList) { + LocalDateTime ldtstart = nattyDateAndTimeParser(reminder.getDateTime().toString()).get(); + LocalDateTime ldtend = nattyDateAndTimeParser(reminder.getEndDateTime().toString()).get(); + LocalDateTime now = LocalDateTime.now(); + if (now.isBefore(ldtend)) { + calendarRNotDue.addEntry(new Entry( + reminder.getReminderText().toString(), new Interval(ldtstart, ldtend))); + } else { + calendarRDue.addEntry(new Entry(reminder.getReminderText().toString(), new Interval(ldtstart, ldtend))); + } + } +``` +###### \java\seedu\address\ui\CalendarPanel.java +``` java + private void setDateAndTime() { + calendarView.setToday(LocalDate.now()); + calendarView.setTime(LocalTime.now()); + calendarView.getCalendarSources().clear(); + } + + private void setupCalendar() { + calendarView.setRequestedTime(LocalTime.now()); + calendarView.setToday(LocalDate.now()); + calendarView.setTime(LocalTime.now()); + calendarView.setShowAddCalendarButton(false); + calendarView.setShowSearchField(false); + calendarView.setShowSearchResultsTray(false); + calendarView.setShowPrintButton(false); + calendarView.showMonthPage(); + } + + public CalendarView getRoot() { + return this.calendarView; + } + +} +``` diff --git a/collated/functional/sham-sheer.md b/collated/functional/sham-sheer.md new file mode 100644 index 000000000000..482052ba211b --- /dev/null +++ b/collated/functional/sham-sheer.md @@ -0,0 +1,599 @@ +# sham-sheer +###### \java\seedu\address\logic\CommandFormatListUtil.java +``` java +/** + * Initialises and returns a list which contains different command formats + */ +public final class CommandFormatListUtil { + private static ArrayList commandFormatList; + + public static ArrayList getCommandFormatList () { + commandFormatList = new ArrayList<>(); + createCommandFormatList(); + return commandFormatList; + } + + /** + * Creates commandFormatList for existing commands + */ + private static void createCommandFormatList() { + commandFormatList.add(AddCommand.COMMAND_FORMAT); + commandFormatList.add(AddGoalCommand.COMMAND_FORMAT); + commandFormatList.add(AddReminderCommand.COMMAND_WORD); + commandFormatList.add(ClearCommand.COMMAND_WORD); + commandFormatList.add(CompleteGoalCommand.COMMAND_WORD); + commandFormatList.add(DeleteCommand.COMMAND_WORD); + commandFormatList.add(DeleteGoalCommand.COMMAND_WORD); + commandFormatList.add(DeleteMeetCommand.COMMAND_WORD); + commandFormatList.add(DeleteReminderCommand.COMMAND_ALIAS_2); + commandFormatList.add(EditCommand.COMMAND_FORMAT); + commandFormatList.add(EditGoalCommand.COMMAND_WORD); + commandFormatList.add(ExitCommand.COMMAND_WORD); + commandFormatList.add(FindCommand.COMMAND_FORMAT); + commandFormatList.add(HelpCommand.COMMAND_WORD); + commandFormatList.add(HistoryCommand.COMMAND_WORD); + commandFormatList.add(ListCommand.COMMAND_WORD); + commandFormatList.add(MeetCommand.COMMAND_WORD); + commandFormatList.add(OngoingGoalCommand.COMMAND_WORD); + commandFormatList.add(RateCommand.COMMAND_WORD); + commandFormatList.add(RedoCommand.COMMAND_WORD); + commandFormatList.add(SelectCommand.COMMAND_WORD); + commandFormatList.add(SortCommand.COMMAND_WORD); + commandFormatList.add(SortGoalCommand.COMMAND_WORD); + commandFormatList.add(ShowLofCommand.COMMAND_WORD); + commandFormatList.add(ThemeCommand.COMMAND_WORD); + commandFormatList.add(UndoCommand.COMMAND_WORD); + + //sorting the commandFormatList + Collections.sort(commandFormatList); + } +} +``` +###### \java\seedu\address\logic\commands\DeleteMeetCommand.java +``` java +/** + * Removes the meet up set with a person using the person's displayed index from CollegeZone. + */ +public class DeleteMeetCommand extends UndoableCommand { + public static final String COMMAND_WORD = "-meet"; + + public static final String COMMAND_ALIAS = "-m"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the person's meet date identified by the index number used in the last person listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "You are not meeting %1$s anymore. "; + + private final Index targetIndex; + + private Person personToDelete; + + public DeleteMeetCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(personToDelete); + try { + model.deleteMeetDate(personToDelete); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target person cannot be missing"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + personToDelete = lastShownList.get(targetIndex.getZeroBased()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteMeetCommand // instanceof handles nulls + && this.targetIndex.equals(((DeleteMeetCommand) other).targetIndex) // state check + && Objects.equals(this.personToDelete, ((DeleteMeetCommand) other).personToDelete)); + } + + +} +``` +###### \java\seedu\address\logic\commands\MeetCommand.java +``` java +/** + * Adds a meeting to CollegeZone. + */ +public class MeetCommand extends UndoableCommand { + public static final String COMMAND_WORD = "meet"; + public static final String COMMAND_ALIAS = "m"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds the date of meetup for the person identified " + + "by the index number used in the last person listing. " + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_DATE + "[REMARK]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_DATE + "01/04/2018"; + + + public static final String MESSAGE_ADD_MEETDATE_SUCCESS = "%1$s added for meet up! Check out your Calendar!"; + public static final String MESSAGE_DELETE_MEETDATE_SUCCESS = "You are not meeting %1$s anymore!!"; + public static final String MESSAGE_DUPLICATE_PERSON = "This person has already been set to have meeting."; + + private final Index targetIndex; + private final Meet date; + + private Person personToEdit; + private Person editedPerson; + + /** + * @param targetIndex of the person in the filtered person list you want to meet + * @param date you want to meet the person + */ + + public MeetCommand(Index targetIndex, Meet date) { + requireNonNull(targetIndex); + requireNonNull(date); + + this.targetIndex = targetIndex; + this.date = date; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(personToEdit); + requireNonNull(editedPerson); + + try { + model.updatePerson(personToEdit, editedPerson); + } catch (DuplicatePersonException dpe) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } catch (PersonNotFoundException pnfe) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + return new CommandResult(generateSuccessMessage(editedPerson)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + personToEdit = lastShownList.get(targetIndex.getZeroBased()); + editedPerson = new Person(personToEdit.getName(), personToEdit.getPhone(), personToEdit.getBirthday(), + personToEdit.getLevelOfFriendship(), personToEdit.getUnitNumber(), personToEdit.getCcas(), + date, personToEdit.getTags()); + } + + /** + * Generates a command execution success message based on whether the remark is added to or removed from + * {@code personToEdit}. + */ + private String generateSuccessMessage(Person personToEdit) { + String message = !date.value.isEmpty() ? MESSAGE_ADD_MEETDATE_SUCCESS : MESSAGE_DELETE_MEETDATE_SUCCESS; + return String.format(message, personToEdit.getName()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof MeetCommand)) { + return false; + } + + // state check + MeetCommand e = (MeetCommand) other; + return targetIndex.equals(e.targetIndex) + && date.equals(e.date); + } + + + + + +} +``` +###### \java\seedu\address\logic\commands\SortCommand.java +``` java +/** + * Sort the persons in CollegeZone based on the users parameters + */ +public class SortCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "sort"; + + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid sort type: %1$s"; + + public static final String MESSAGE_EMPTY_LIST = "CollegeZone student list is empty, There is nothing to sort!"; + + public static final String MESSAGE_SORTED_SUCCESS_LEVEL_OF_FRIENDSHIP = "List sorted according to Friendship lvl!"; + + public static final String MESSAGE_SORTED_SUCCESS_MEET_DATE = "List sorted according to your latest meet date!"; + + public static final String MESSAGE_SORTED_SUCCESS_BIRTHDAY = "List sorted according to show latest birthday!"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sorts the person list identified by the index number used in the last person listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + private final Index index; + + private final ObservableList internalList = FXCollections.observableArrayList(); + + public SortCommand(Index index) { + requireNonNull(index); + this.index = index; + } + + + @Override + public CommandResult executeUndoableCommand() { + try { + model.sortPersons(index); + } catch (IndexOutOfBoundsException ioe) { + throw new AssertionError("The index is out of bounds"); + } + if (index.getOneBased() == 1) { + return new CommandResult(String.format(MESSAGE_SORTED_SUCCESS_LEVEL_OF_FRIENDSHIP)); + } + if (index.getOneBased() == 2) { + return new CommandResult(String.format(MESSAGE_SORTED_SUCCESS_MEET_DATE)); + } + return new CommandResult(String.format(MESSAGE_SORTED_SUCCESS_BIRTHDAY)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (index.getOneBased() > 3) { + throw new CommandException(String.format(SortCommand.MESSAGE_INVALID_COMMAND_FORMAT, index.getOneBased())); + } + if (lastShownList.size() == 0) { + throw new CommandException(String.format(SortCommand.MESSAGE_EMPTY_LIST)); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortCommand // instanceof handles nulls + && this.index.equals(((SortCommand) other).index)); // state check + } + + +} + +``` +###### \java\seedu\address\logic\parser\MeetCommandParser.java +``` java +/** + * Parses input arguments and creates a new {@code RemarkCommand} object + */ +public class MeetCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the {@code MeetCommand} + * and returns a {@code MeetCommand} object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public MeetCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_DATE); + + Index index; + + if (!arePrefixesPresent(argMultimap, PREFIX_DATE) || argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MeetCommand.MESSAGE_USAGE)); + } + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + Meet meetDate = ParserUtil.parseMeetDate(argMultimap.getValue(PREFIX_DATE)).get(); + return new MeetCommand(index, meetDate); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String unitNumber} into an {@code UnitNumber}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code unitNumber} is invalid. + */ + public static Meet parseMeetDate(String meetDate) throws IllegalValueException { + requireNonNull(meetDate); + String trimmedMeetDate = meetDate.trim(); + if (!Meet.isValidDate(meetDate)) { + throw new IllegalValueException(Meet.MESSAGE_DATE_CONSTRAINTS); + } + return new Meet(trimmedMeetDate); + } + /** + * Parses a {@code Optional meetDate} into an {@code Optional} if {@code meetDate} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseMeetDate(Optional meetDate) throws IllegalValueException { + requireNonNull(meetDate); + return meetDate.isPresent() ? Optional.of(parseMeetDate(meetDate.get())) : Optional.empty(); + } + +``` +###### \java\seedu\address\logic\parser\SortCommandParser.java +``` java +/** + * Parses input arguments and creates a new DeleteCommand object + */ +public class SortCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteCommand + * and returns an DeleteCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SortCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new SortCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + } + +} +``` +###### \java\seedu\address\model\AddressBook.java +``` java + public void sortPersons(Index index) throws IndexOutOfBoundsException { + this.persons.sortPersons(index); + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public void deleteMeetDate (Person person) throws PersonNotFoundException { + addressBook.removeMeetFromPerson(person); + indicateAddressBookChanged(); + } + + @Override + public synchronized void sortPersons(Index index) throws IndexOutOfBoundsException { + addressBook.sortPersons(index); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + indicateAddressBookChanged(); + } + + + //=========== Filtered Person List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * {@code addressBook} + */ +``` +###### \java\seedu\address\model\person\Birthday.java +``` java + /** + * Converts Birth date to a time that is relative to current date, for sorting purposes + */ + public static long birthDateToInt(String date) { + Calendar calendar = Calendar.getInstance(); + long longDate = convertbirthDateToSeconds(date.toString()); + long currentDate = calendar.getTimeInMillis(); + long timeDiff = longDate - currentDate; + if (timeDiff < 0) { + return Long.MAX_VALUE; + } else { + return timeDiff; + } + } + + /** + * Converts Birth date to seconds + */ + public static long convertbirthDateToSeconds(String date) { + if (date == "") { + return 0; + } + int day = Integer.parseInt(date.toString().substring(0, + 2)); + int month = Integer.parseInt(date.toString().substring(3, + 5)); + int year = 2018; + Calendar calendar = new GregorianCalendar(); + calendar.set(year, month - 1, day); + long seconds = calendar.getTimeInMillis(); + return seconds; + } + +``` +###### \java\seedu\address\model\person\Meet.java +``` java +/** + * Represents a Person's date of meeting in the address book. + * Guarantees: immutable; is always valid + */ +public class Meet { + public static final String MESSAGE_DATE_CONSTRAINTS = + "Make sure date is in this format: DD/MM/YYYY"; + public static final String DATE_VALIDATION_REGEX = + "^(((0[1-9]|[12]\\d|3[01])\\/(0[13578]|1[02])\\/((19|[2-9]\\d)\\d{2}))|((0[1-9]|[12]\\d|30)\\/" + + "(0[13456789]|1[012])\\/((19|[2-9]\\d)\\d{2}))|((0[1-9]|1\\d|2[0-8])\\/02\\/((19|[2-9]\\d)\\" + + "d{2}))|(29\\/02\\/((1[6-9]|[2-9]\\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579]" + + "[26])00))))$"; + + public final String value; + + public Meet(String meet) { + requireNonNull(meet); + if (meet.isEmpty()) { + this.value = ""; + } else { + checkArgument(isValidDate(meet), MESSAGE_DATE_CONSTRAINTS); + this.value = meet; + } + } + /** + * Converts date to seconds + */ + public static long convertDateToSeconds(String date) { + if (date == "") { + return 0; + } + int day = Integer.parseInt(date.toString().substring(0, + 2)); + int month = Integer.parseInt(date.toString().substring(3, + 5)); + int year = Integer.parseInt(date.toString().substring(6, + 10)); + Calendar calendar = new GregorianCalendar(); + calendar.set(year, month - 1, day); + long seconds = calendar.getTimeInMillis(); + return seconds; + } + + /** + * Converts meet date to a time that is relative to current date, for sorting purposes + */ + public static long dateToInt(String date) { + Calendar calendar = Calendar.getInstance(); + long longDate = convertDateToSeconds(date.toString()); + long currentDate = calendar.getTimeInMillis(); + long timeDiff = longDate - currentDate; + if (timeDiff < 0) { + return Long.MAX_VALUE; + } else { + return timeDiff; + } + } + + + public static boolean isValidDate(String test) { + return test.matches(DATE_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Meet // instanceof handles nulls + && this.value.equals(((Meet) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + + } +} +``` +###### \java\seedu\address\model\person\UniquePersonList.java +``` java + /** + * Sorting method + */ + public void sortExecution(Index index, ObservableList internalList) { + requireNonNull(index); + if (index.getOneBased() > 3) { + throw new IndexOutOfBoundsException(); + } + if (index.getOneBased() == 1) { + Comparator comparator = Comparator.comparingInt(Person::getLevelOfFriendshipInt); + FXCollections.sort(internalList, comparator); + FXCollections.reverse(internalList); + } + if (index.getOneBased() == 2) { + Comparator comparator = Comparator.comparingLong(Person::getMeetDateInt); + FXCollections.sort(internalList, comparator); + } + if (index.getOneBased() == 3) { + Comparator comparator = Comparator.comparingLong(Person::getBirthdayInt); + FXCollections.sort(internalList, comparator); + } + } +} +``` +###### \java\seedu\address\storage\XmlAdaptedPerson.java +``` java + final Meet meetDate = new Meet(this.meetDate); + +``` +###### \java\seedu\address\ui\CalendarPanel.java +``` java + for (Person person : personList) { + String meetDate = person.getMeetDate().toString(); + if (!meetDate.isEmpty()) { + int day = Integer.parseInt(meetDate.substring(0, + 2)); + int month = Integer.parseInt(meetDate.substring(3, + 5)); + int year = Integer.parseInt(meetDate.substring(6, + 10)); + calendarM.addEntry(new Entry("Meeting " + person.getName().toString(), + new Interval(LocalDate.of(year, month, day), LocalTime.of(12, 0), + LocalDate.of(year, month, day), LocalTime.of(13, 0)))); + } + } + calendarView.getCalendarSources().add(myCalendarSource); + } + +``` +###### \java\seedu\address\ui\CommandBox.java +``` java + /** + * Sets the commandbox to completed command format if the entered substring of the command is valid + * @param text is the command which is to be autocompleted + */ + private void autocompleteCommand(String text) { + ArrayList commandFormatList = CommandFormatListUtil.getCommandFormatList(); + + //retrieve the list of words which begin with text + List autocompleteCommandList = commandFormatList.stream() + .filter(s -> s.startsWith(text)) + .collect(Collectors.toList()); + + //replace input in text field with matched keyword + if (!autocompleteCommandList.isEmpty()) { + replaceText(autocompleteCommandList.get(0)); + } + + } + +} +``` diff --git a/collated/functional/zuweitrack.md b/collated/functional/zuweitrack.md new file mode 100644 index 000000000000..4da5310ecb93 --- /dev/null +++ b/collated/functional/zuweitrack.md @@ -0,0 +1,341 @@ +# zuweitrack +###### \java\seedu\address\logic\commands\RateCommand.java +``` java +/** + * Rates existing person(s) in CollegeZone. + */ +public class RateCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "rate"; + + public static final String COMMAND_ALIAS = "rt"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Rates person(s) in College Zone " + + "and changes the level of friendship " + + "by the index number used in the latest listing.\n" + + "Existing level of friendship will be overwritten by the input values.\n" + + "Parameters: INDEX(s) (must be a positive integer) " + + "[" + PREFIX_LEVEL_OF_FRIENDSHIP + "LEVELOFFRIENDSHIP] (between 1 and 10)\n" + + "Example: " + COMMAND_WORD + " 1 3 " + + PREFIX_LEVEL_OF_FRIENDSHIP + "5 "; + + private static final String MESSAGE_EDIT_PERSON_SUCCESS = "Rated person(s) successfully"; + private static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + private static final String MESSAGE_PERSON_NOT_FOUND = "The selected person cannot be missing"; + private static final String MESSAGE_ONE_OR_MORE_INVALID_INDEX = + "One or more index inputs may not be valid" + + " and only the person(s) of valid indexes are being rated!"; + + private final List indexList; + private final String levelOfFriendship; + + /** + * @param indexList list of index(es) of the person in the filtered person list + * @param levelOfFriendship new level of friendship to add to the person + */ + public RateCommand(List indexList, String levelOfFriendship) { + requireNonNull(indexList); + requireNonNull(levelOfFriendship); + + this.indexList = indexList; + this.levelOfFriendship = levelOfFriendship; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + List latestList = model.getFilteredPersonList(); + for (Index index : indexList) { + + if (index.getZeroBased() >= latestList.size()) { + throw new CommandException(MESSAGE_ONE_OR_MORE_INVALID_INDEX); + } + + Person selectedPerson = latestList.get(index.getZeroBased()); + + try { + Person editedPerson = new Person(selectedPerson.getName(), selectedPerson.getPhone(), + selectedPerson.getBirthday(), new LevelOfFriendship(levelOfFriendship), + selectedPerson.getUnitNumber(), + selectedPerson.getCcas(), selectedPerson.getMeetDate(), selectedPerson.getTags()); + model.updatePerson(selectedPerson, editedPerson); + + } catch (PersonNotFoundException pnfe) { + throw new CommandException(MESSAGE_PERSON_NOT_FOUND); + } catch (DuplicatePersonException dpe) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + + } + + } + + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + return new CommandResult(MESSAGE_EDIT_PERSON_SUCCESS); + + } + +} +``` +###### \java\seedu\address\logic\commands\SeekRaCommand.java +``` java +/** + * Finds and lists the Resident Assistant (RA) of an individual RC Student + * in address book whose name contains any of the argument keywords. + * Keyword matching is case sensitive. + */ +public class SeekRaCommand extends Command { + + public static final String COMMAND_WORD = "seek"; + + public static final String COMMAND_ALIAS = "sk"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Seeks and lists all Resident Assistants (RA) of RC4 with the" + + " individual RC student whose name contain any of " + + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " alice bob charlie"; + + private final UnitNumberContainsKeywordsPredicate predicate; + + public SeekRaCommand(UnitNumberContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute() { + model.updateFilteredPersonList(predicate); + return new CommandResult(getMessageForRaShownSummary(model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SeekRaCommand // instanceof handles nulls + && this.predicate.equals(((SeekRaCommand) other).predicate)); // state check + } +} +``` +###### \java\seedu\address\logic\commands\ShowLofCommand.java +``` java +/** + * Finds and lists the person(s) + * in address book whose level of friendship matches the input value + * of the argument keywords. + */ +public class ShowLofCommand extends Command { + + public static final String COMMAND_WORD = "show"; + + public static final String COMMAND_ALIAS = "sh"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Shows person(s) in CollegeZone with the" + + " whose level of friendship contains any of " + + "specified level and displays them as a list with index numbers.\n" + + "Parameters: LEVELOFFRIENDSHIP [MORE_LEVELOFFRIENDSHIP]...\n" + + "Example: " + COMMAND_WORD + " 1 2 7"; + + private final LofContainsValuePredicate predicate; + + public ShowLofCommand(LofContainsValuePredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute() { + model.updateFilteredPersonList(predicate); + return new CommandResult(getMessageForPersonListShownSummary(model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ShowLofCommand // instanceof handles nulls + && this.predicate.equals(((ShowLofCommand) other).predicate)); // state check + } +} +``` +###### \java\seedu\address\logic\parser\RateCommandParser.java +``` java +/** + * Parses input arguments and creates a new RateCommand object + */ +public class RateCommandParser { + + /** + * Returns true if the level of friendship prefix "/*" is present + */ + private static boolean isPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix prefix) { + return Stream.of(prefix).allMatch(groupPrefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if the level of friendship is between 1 - 10 + */ + private static boolean containsValidRange(String levelOfFriendship) { + return levelOfFriendship.matches("0?[1-9]|[1][0]"); + } + + /** + * Parses the given {@code String} of arguments in the context of the RateCommand + * and returns an RateCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RateCommand parse (String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(args, PREFIX_LEVEL_OF_FRIENDSHIP); + + if (!isPrefixesPresent(argumentMultimap, PREFIX_LEVEL_OF_FRIENDSHIP)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RateCommand.MESSAGE_USAGE)); + } + String levelOfFriendship = argumentMultimap.getValue(PREFIX_LEVEL_OF_FRIENDSHIP).get(); + + if (!containsValidRange(levelOfFriendship)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RateCommand.MESSAGE_USAGE)); + } + + String preamble; + String[] indexString; + List indexList = new ArrayList<>(); + + try { + preamble = argumentMultimap.getPreamble(); + indexString = preamble.split("\\s+"); + for (String index : indexString) { + indexList.add(ParserUtil.parseIndex(index)); + } + + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RateCommand.MESSAGE_USAGE)); + } + + return new RateCommand(indexList, new String(levelOfFriendship)); + } + +} +``` +###### \java\seedu\address\logic\parser\SeekRaCommandParser.java +``` java +/** + * Parses input arguments and creates a new SeekRaCommand object + */ +public class SeekRaCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the SeekRaCommand + * and returns an SeekRaCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SeekRaCommand parse(String args) throws ParseException { + + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SeekRaCommand.MESSAGE_USAGE)); + } + + trimmedArgs = trimmedArgs + " " + "RA"; + + String[] nameKeywords = (trimmedArgs.split("\\s+")); + + return new SeekRaCommand(new UnitNumberContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + } + +} +``` +###### \java\seedu\address\logic\parser\ShowLofCommandParser.java +``` java +/** + * Parses input arguments and creates a new ShowLofCommand object + */ +public class ShowLofCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ShowLofCommand + * and returns an ShowLofCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ShowLofCommand parse(String args) throws ParseException { + + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ShowLofCommand.MESSAGE_USAGE)); + } + + String[] nameKeywords = (trimmedArgs.split("\\s+")); + + return new ShowLofCommand(new LofContainsValuePredicate(Arrays.asList(nameKeywords))); + } + +} +``` +###### \java\seedu\address\model\person\LofContainsValuePredicate.java +``` java +/** + * Tests that a {@code Person}'s {@code UnitNumber} matches any of the keywords given. + */ +public class LofContainsValuePredicate implements Predicate { + private final List keywords; + + public LofContainsValuePredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase + (person.getLevelOfFriendship().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LofContainsValuePredicate // instanceof handles nulls + && this.keywords.equals(((LofContainsValuePredicate) other).keywords)); // state check + } + +} +``` +###### \java\seedu\address\model\person\UnitNumberContainsKeywordsPredicate.java +``` java +/** + * Tests that a {@code Person}'s {@code UnitNumber} matches any of the keywords given. + */ +public class UnitNumberContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public UnitNumberContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + + Iterator ir = person.getTags().iterator(); + StringBuilder tag = new StringBuilder(); + while (ir.hasNext()) { + tag.append(ir.next().tagName); + tag.append(" "); + } + + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)) + | keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(tag.toString(), keyword)); + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UnitNumberContainsKeywordsPredicate // instanceof handles nulls + && this.keywords.equals(((UnitNumberContainsKeywordsPredicate) other).keywords)); // state check + } + +} +``` diff --git a/collated/main/deborahlow97.md b/collated/main/deborahlow97.md new file mode 100644 index 000000000000..de79eba0e3b6 --- /dev/null +++ b/collated/main/deborahlow97.md @@ -0,0 +1,4160 @@ +# deborahlow97 +###### \java\seedu\address\commons\events\ui\ThemeSwitchRequestEvent.java +``` java +/** + * Indicates that a theme switch is requested. + */ +public class ThemeSwitchRequestEvent extends BaseEvent { + public final String themeToChangeTo; + + public ThemeSwitchRequestEvent(String themeToChangeTo) { + this.themeToChangeTo = themeToChangeTo; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} +``` +###### \java\seedu\address\logic\commands\AddGoalCommand.java +``` java +/** + * Adds a goal to CollegeZone. + */ +public class AddGoalCommand extends UndoableCommand { + public static final String COMMAND_WORD = "+goal"; + public static final String COMMAND_ALIAS_1 = "+g"; + public static final String COMMAND_ALIAS_2 = "addgoal"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " " + + PREFIX_IMPORTANCE + " " + + PREFIX_GOAL_TEXT + " "; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a goal to Goals Page. \n" + + "Parameters: " + + PREFIX_IMPORTANCE + "IMPORTANCE " + + PREFIX_GOAL_TEXT + "TEXT \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_IMPORTANCE + "3 " + + PREFIX_GOAL_TEXT + "lose weight \n"; + + public static final String MESSAGE_SUCCESS = "New goal added: %1$s"; + public static final String MESSAGE_DUPLICATE_GOAL = "This goal already exists in the Goals Page"; + + private final Goal toAdd; + + /** + * Creates an AddGoalCommand to add the specified {@code Goal} + */ + public AddGoalCommand(Goal goal) { + requireNonNull(goal); + toAdd = goal; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + model.addGoal(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicateGoalException e) { + throw new CommandException(MESSAGE_DUPLICATE_GOAL); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddGoalCommand // instanceof handles nulls + && toAdd.equals(((AddGoalCommand) other).toAdd)); + } +} +``` +###### \java\seedu\address\logic\commands\CompleteGoalCommand.java +``` java +/** + * Edits the details of an existing goal in CollegeZone. + */ +public class CompleteGoalCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "!goal"; + public static final String COMMAND_ALIAS_1 = "!g"; + public static final String COMMAND_ALIAS_2 = "completegoal"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Indicate completion of the goal identified " + + "by the index number used in the last goal listing.\n " + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 "; + + public static final String MESSAGE_COMPLETE_GOAL_SUCCESS = "Completed Goal! : %1$s"; + + private final Index index; + private final CompleteGoalDescriptor completeGoalDescriptor; + + private Goal goalToUpdate; + private Goal updatedGoal; + + /** + * @param index of the goal in the filtered goal list to update + */ + public CompleteGoalCommand(Index index, CompleteGoalDescriptor completeGoalDescriptor) { + requireNonNull(index); + requireNonNull(completeGoalDescriptor); + + this.index = index; + this.completeGoalDescriptor = new CompleteGoalDescriptor(completeGoalDescriptor); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.updateGoalWithoutParameters(goalToUpdate, updatedGoal); + } catch (GoalNotFoundException pnfe) { + throw new AssertionError("The target goal cannot be missing"); + } + model.updateFilteredGoalList(PREDICATE_SHOW_ALL_GOALS); + return new CommandResult(String.format(MESSAGE_COMPLETE_GOAL_SUCCESS, updatedGoal)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredGoalList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + } + + goalToUpdate = lastShownList.get(index.getZeroBased()); + if (goalToUpdate.getCompletion().hasCompleted) { + throw new CommandException(Messages.MESSAGE_GOAL_COMPLETED_ERROR); + } + updatedGoal = createUpdatedGoal(goalToUpdate, completeGoalDescriptor); + } + + /** + * Creates and returns a {@code Goal} with the details of {@code goalToUpdate} + * edited with {@code completeGoalDescriptor}. + */ + private static Goal createUpdatedGoal(Goal goalToUpdate, CompleteGoalDescriptor completeGoalDescriptor) { + assert goalToUpdate != null; + + GoalText goalText = goalToUpdate.getGoalText(); + Importance importance = goalToUpdate.getImportance(); + StartDateTime startDateTime = goalToUpdate.getStartDateTime(); + EndDateTime updatedEndDateTime = completeGoalDescriptor.getEndDateTime() + .orElse(goalToUpdate.getEndDateTime()); + Completion updatedCompletion = completeGoalDescriptor.getCompletion().orElse(goalToUpdate.getCompletion()); + + return new Goal(importance, goalText, startDateTime, updatedEndDateTime, updatedCompletion); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof CompleteGoalCommand)) { + return false; + } + + // state check + CompleteGoalCommand e = (CompleteGoalCommand) other; + return index.equals(e.index) + && completeGoalDescriptor.equals(e.completeGoalDescriptor) + && Objects.equals(goalToUpdate, e.goalToUpdate); + } + + /** + * Stores the details to update the goal with. + */ + public static class CompleteGoalDescriptor { + + private EndDateTime endDateTime; + private Completion completion; + + public CompleteGoalDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code toCopy} is used internally. + */ + public CompleteGoalDescriptor(CompleteGoalDescriptor toCopy) { + setEndDateTime(toCopy.endDateTime); + setCompletion(toCopy.completion); + } + + public void setEndDateTime(EndDateTime endDateTime) { + this.endDateTime = endDateTime; + } + + public Optional getEndDateTime() { + return Optional.ofNullable(endDateTime); + } + + public void setCompletion(Completion completion) { + this.completion = completion; + } + + public Optional getCompletion() { + return Optional.ofNullable(completion); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof CompleteGoalDescriptor)) { + return false; + } + + // state check + CompleteGoalDescriptor e = (CompleteGoalDescriptor) other; + + return getEndDateTime().equals(e.getEndDateTime()) + && getCompletion().equals(e.getCompletion()); + } + } +} +``` +###### \java\seedu\address\logic\commands\DeleteGoalCommand.java +``` java +/** + * Deletes a goal identified using it's last displayed index from CollegeZone. + */ +public class DeleteGoalCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "-goal"; + + public static final String COMMAND_ALIAS_1 = "-g"; + + public static final String COMMAND_ALIAS_2 = "deletegoal"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the goal identified by the index number used in the goal listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_GOAL_SUCCESS = "Deleted Goal: %1$s"; + + private final Index targetIndex; + + private Goal goalToDelete; + + public DeleteGoalCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(goalToDelete); + try { + model.deleteGoal(goalToDelete); + } catch (GoalNotFoundException pnfe) { + throw new AssertionError("The target goal cannot be missing"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_GOAL_SUCCESS, goalToDelete)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredGoalList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + } + + goalToDelete = lastShownList.get(targetIndex.getZeroBased()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteGoalCommand // instanceof handles nulls + && this.targetIndex.equals(((DeleteGoalCommand) other).targetIndex) // state check + && Objects.equals(this.goalToDelete, ((DeleteGoalCommand) other).goalToDelete)); + } +} +``` +###### \java\seedu\address\logic\commands\EditGoalCommand.java +``` java +/** + * Edits the details of an existing goal in CollegeZone. + */ +public class EditGoalCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "~goal"; + public static final String COMMAND_ALIAS_1 = "~g"; + public static final String COMMAND_ALIAS_2 = "editgoal"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the goal identified " + + "by the index number used in the last goal listing. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_GOAL_TEXT + "GOAL TEXT] " + + "[" + PREFIX_IMPORTANCE + "IMPORTANCE] \n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_IMPORTANCE + "2 "; + + public static final String MESSAGE_EDIT_GOAL_SUCCESS = "Edited Goal: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_GOAL = "This goal already exists in the address book."; + + private final Index index; + private final EditGoalDescriptor editGoalDescriptor; + + private Goal goalToEdit; + private Goal editedGoal; + + /** + * @param index of the goal in the filtered goal list to edit + * @param editGoalDescriptor details to edit the goal with + */ + public EditGoalCommand(Index index, EditGoalDescriptor editGoalDescriptor) { + requireNonNull(index); + requireNonNull(editGoalDescriptor); + + this.index = index; + this.editGoalDescriptor = new EditGoalDescriptor(editGoalDescriptor); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.updateGoal(goalToEdit, editedGoal); + } catch (DuplicateGoalException dpe) { + throw new CommandException(MESSAGE_DUPLICATE_GOAL); + } catch (GoalNotFoundException pnfe) { + throw new AssertionError("The target goal cannot be missing"); + } + model.updateFilteredGoalList(PREDICATE_SHOW_ALL_GOALS); + return new CommandResult(String.format(MESSAGE_EDIT_GOAL_SUCCESS, editedGoal)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredGoalList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + } + + goalToEdit = lastShownList.get(index.getZeroBased()); + editedGoal = createEditedGoal(goalToEdit, editGoalDescriptor); + } + + /** + * Creates and returns a {@code Goal} with the details of {@code goalToEdit} + * edited with {@code editGoalDescriptor}. + */ + private static Goal createEditedGoal(Goal goalToEdit, EditGoalDescriptor editGoalDescriptor) { + assert goalToEdit != null; + + GoalText updatedGoalText = editGoalDescriptor.getGoalText().orElse(goalToEdit.getGoalText()); + Importance updatedImportance = editGoalDescriptor.getImportance().orElse(goalToEdit.getImportance()); + StartDateTime startDateTime = goalToEdit.getStartDateTime(); + EndDateTime endDateTime = goalToEdit.getEndDateTime(); + Completion completion = goalToEdit.getCompletion(); + return new Goal(updatedImportance, updatedGoalText, startDateTime, endDateTime, completion); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditGoalCommand)) { + return false; + } + + // state check + EditGoalCommand e = (EditGoalCommand) other; + return index.equals(e.index) + && editGoalDescriptor.equals(e.editGoalDescriptor) + && Objects.equals(goalToEdit, e.goalToEdit); + } + + /** + * Stores the details to edit the goal with. Each non-empty field value will replace the + * corresponding field value of the goal. + */ + public static class EditGoalDescriptor { + private GoalText goalText; + private Importance importance; + + public EditGoalDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditGoalDescriptor(EditGoalDescriptor toCopy) { + setGoalText(toCopy.goalText); + setImportance(toCopy.importance); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(this.goalText, this.importance); + } + + public void setGoalText(GoalText goalText) { + this.goalText = goalText; + } + + public Optional getGoalText() { + return Optional.ofNullable(goalText); + } + + public void setImportance(Importance importance) { + this.importance = importance; + } + + public Optional getImportance() { + return Optional.ofNullable(importance); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditGoalDescriptor)) { + return false; + } + + // state check + EditGoalDescriptor e = (EditGoalDescriptor) other; + + return getGoalText().equals(e.getGoalText()) + && getImportance().equals(e.getImportance()); + } + } +} + +``` +###### \java\seedu\address\logic\commands\OngoingGoalCommand.java +``` java +/** + * Edits the details of an existing goal in CollegeZone. + */ +public class OngoingGoalCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "-!goal"; + public static final String COMMAND_ALIAS_1 = "-!g"; + public static final String COMMAND_ALIAS_2 = "ongoinggoal"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Indicate identified goal is not completed " + + "and still ongoing.\n" + + "Goal is identified " + + "by the index number used in the last goal listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 "; + + public static final String MESSAGE_ONGOING_GOAL_SUCCESS = "Ongoing Goal! : %1$s"; + + private final Index index; + private final OngoingGoalDescriptor ongoingGoalDescriptor; + + private Goal goalToUpdate; + private Goal updatedGoal; + + /** + * @param index of the goal in the filtered goal list to update + */ + public OngoingGoalCommand(Index index, OngoingGoalDescriptor ongoingGoalDescriptor) { + requireNonNull(index); + requireNonNull(ongoingGoalDescriptor); + + this.index = index; + this.ongoingGoalDescriptor = new OngoingGoalDescriptor(ongoingGoalDescriptor); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.updateGoalWithoutParameters(goalToUpdate, updatedGoal); + } catch (GoalNotFoundException pnfe) { + throw new AssertionError("The target goal cannot be missing"); + } + model.updateFilteredGoalList(PREDICATE_SHOW_ALL_GOALS); + return new CommandResult(String.format(MESSAGE_ONGOING_GOAL_SUCCESS, updatedGoal)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredGoalList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + } + + goalToUpdate = lastShownList.get(index.getZeroBased()); + if (!goalToUpdate.getCompletion().hasCompleted) { + throw new CommandException(Messages.MESSAGE_GOAL_ONGOING_ERROR); + } + updatedGoal = createUpdatedGoal(goalToUpdate, ongoingGoalDescriptor); + } + + /** + * Creates and returns a {@code Goal} with the details of {@code goalToUpdate} + * edited with {@code ongoingGoalDescriptor}. + */ + private static Goal createUpdatedGoal(Goal goalToUpdate, OngoingGoalDescriptor ongoingGoalDescriptor) { + assert goalToUpdate != null; + + GoalText goalText = ongoingGoalDescriptor.getGoalText().orElse(goalToUpdate.getGoalText()); + Importance importance = ongoingGoalDescriptor.getImportance().orElse(goalToUpdate.getImportance()); + StartDateTime startDateTime = ongoingGoalDescriptor.getStartDateTime().orElse(goalToUpdate.getStartDateTime()); + EndDateTime updatedEndDateTime = ongoingGoalDescriptor.getEndDateTime() + .orElse(goalToUpdate.getEndDateTime()); + Completion updatedCompletion = ongoingGoalDescriptor.getCompletion().orElse(goalToUpdate.getCompletion()); + + return new Goal(importance, goalText, startDateTime, updatedEndDateTime, updatedCompletion); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof OngoingGoalCommand)) { + return false; + } + + // state check + OngoingGoalCommand e = (OngoingGoalCommand) other; + return index.equals(e.index) + && ongoingGoalDescriptor.equals(e.ongoingGoalDescriptor) + && Objects.equals(goalToUpdate, e.goalToUpdate); + } + + /** + * Stores the details to update the goal with. + */ + public static class OngoingGoalDescriptor { + private GoalText goalText; + private Importance importance; + private StartDateTime startDateTime; + private EndDateTime endDateTime; + private Completion completion; + + public OngoingGoalDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code toCopy} is used internally. + */ + public OngoingGoalDescriptor(OngoingGoalDescriptor toCopy) { + setEndDateTime(toCopy.endDateTime); + setCompletion(toCopy.completion); + } + + public void setEndDateTime(EndDateTime endDateTime) { + this.endDateTime = endDateTime; + } + + public Optional getEndDateTime() { + return Optional.ofNullable(endDateTime); + } + + public void setCompletion(Completion completion) { + this.completion = completion; + } + + public Optional getCompletion() { + return Optional.ofNullable(completion); + } + + public Optional getStartDateTime() { + return Optional.ofNullable(startDateTime); + } + + public Optional getImportance() { + return Optional.ofNullable(importance); + } + public Optional getGoalText() { + return Optional.ofNullable(goalText); + } + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof OngoingGoalDescriptor)) { + return false; + } + + // state check + OngoingGoalDescriptor e = (OngoingGoalDescriptor) other; + + return getGoalText().equals(e.getGoalText()) + && getImportance().equals(e.getImportance()) + && getStartDateTime().equals(e.getStartDateTime()) + && getEndDateTime().equals(e.getEndDateTime()) + && getCompletion().equals(e.getCompletion()); + } + } +} +``` +###### \java\seedu\address\logic\commands\SortGoalCommand.java +``` java +/** + * Sorts goal list in CollegeZone based on sort field entered by user. + */ +public class SortGoalCommand extends Command { + + public static final String COMMAND_WORD = "sortgoal"; + public static final String COMMAND_ALIAS = "sgoal"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sorts CollegeZone's goals based on the field entered.\n" + + "Parameters: " + + PREFIX_SORT_FIELD + "FIELD (must be 'importance', 'startdatetime' or 'completion') " + + PREFIX_SORT_ORDER + "ORDER (must be either 'ascending' or 'descending')\n" + + "Example: " + COMMAND_WORD + " f/completion o/ascending"; + + public static final String MESSAGE_SUCCESS = "Sorted all goals by %s and %s"; + private String sortField; + private String sortOrder; + + public SortGoalCommand(String field, String order) { + this.sortField = field; + this.sortOrder = order; + } + + @Override + public CommandResult execute() throws CommandException { + try { + model.sortGoal(sortField, sortOrder); + } catch (EmptyGoalListException egle) { + throw new CommandException(Messages.MESSAGE_INVALID_SORT_COMMAND_USAGE); + } + model.updateFilteredGoalList(PREDICATE_SHOW_ALL_GOALS); + return new CommandResult(String.format(MESSAGE_SUCCESS, sortField, sortOrder)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortGoalCommand // instanceof handles nulls + && sortField.equals(((SortGoalCommand) other).sortField)); + } +} +``` +###### \java\seedu\address\logic\commands\ThemeCommand.java +``` java +/** + * Changes the CollegeZone colour theme to either dark, bubblegum or light. + */ +public class ThemeCommand extends Command { + public static final String COMMAND_WORD = "theme"; + public static final String COMMAND_ALIAS = "th"; + public static final String MESSAGE_SUCCESS = "Theme successfully changed!"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Changes the theme to the theme word entered.\n" + + "Parameters: COLOUR THEME\n" + + "(Colour theme words: dark, bubblegum, light)\n" + + "Example: " + COMMAND_WORD + " dark\n"; + public static final String MESSAGE_INVALID_THEME_COLOUR = "Theme colour entered is invalid.\n" + + "Possible theme colours:\n" + + "(Colour theme words: dark, bubblegum, light)\n"; + private final String themeColour; + + /** + * Creates a ThemeCommand based on the specified themeColour. + */ + public ThemeCommand (String themeColour) { + requireNonNull(themeColour); + this.themeColour = themeColour; + } + + @Override + public CommandResult execute() { + + EventsCenter.getInstance().post(new ThemeSwitchRequestEvent(themeColour)); + return new CommandResult(String.format(MESSAGE_SUCCESS, themeColour)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ThemeCommand // instanceof handles nulls + && themeColour.equals(((ThemeCommand) other).themeColour)); + } +} +``` +###### \java\seedu\address\logic\parser\AddCommandParser.java +``` java + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an AddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_BIRTHDAY, + PREFIX_LEVEL_OF_FRIENDSHIP, PREFIX_UNIT_NUMBER, PREFIX_CCA, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_BIRTHDAY, PREFIX_PHONE, PREFIX_UNIT_NUMBER, + PREFIX_LEVEL_OF_FRIENDSHIP, PREFIX_UNIT_NUMBER) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + } + + try { + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME)).get(); + Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE)).get(); + Birthday birthday = ParserUtil.parseBirthday(argMultimap.getValue(PREFIX_BIRTHDAY)).get(); + UnitNumber unitNumber = ParserUtil.parseUnitNumber(argMultimap.getValue(PREFIX_UNIT_NUMBER)).get(); + LevelOfFriendship levelOfFriendship = ParserUtil.parseLevelOfFriendship(argMultimap + .getValue(PREFIX_LEVEL_OF_FRIENDSHIP)).get(); + Set ccaList = ParserUtil.parseCcas(argMultimap.getAllValues(PREFIX_CCA)); + Meet meetDate = new Meet(""); + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + Person person = new Person(name, phone, birthday, levelOfFriendship, unitNumber, ccaList, meetDate, + tagList); + return new AddCommand(person); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + +``` +###### \java\seedu\address\logic\parser\AddGoalCommandParser.java +``` java +/** + * Parses input arguments and creates a new AddGoalCommand object + */ +public class AddGoalCommandParser implements Parser { + + public static final String EMPTY_END_DATE_TIME = ""; + public static final boolean INITIAL_COMPLETION_STATUS = false; + /** + * Parses the given {@code String} of arguments in the context of the AddGoalCommand + * and returns an AddGoalCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddGoalCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_IMPORTANCE, PREFIX_GOAL_TEXT); + + if (!arePrefixesPresent(argMultimap, PREFIX_IMPORTANCE, PREFIX_GOAL_TEXT) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddGoalCommand.MESSAGE_USAGE)); + } + + try { + Importance importance = ParserUtil.parseImportance(argMultimap.getValue(PREFIX_IMPORTANCE)).get(); + GoalText goalText = ParserUtil.parseGoalText(argMultimap.getValue(PREFIX_GOAL_TEXT)).get(); + StartDateTime startDateTime = new StartDateTime(LocalDateTime.now()); + EndDateTime endDateTime = new EndDateTime(EMPTY_END_DATE_TIME); + Completion completion = new Completion(INITIAL_COMPLETION_STATUS); + Goal goal = new Goal(importance, goalText, startDateTime, endDateTime, completion); + return new AddGoalCommand(goal); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} +``` +###### \java\seedu\address\logic\parser\CompleteGoalCommandParser.java +``` java +/** + * Parses input arguments and creates a new CompleteGoalCommand object + */ +public class CompleteGoalCommandParser implements Parser { + + public static final boolean COMPLETED_BOOLEAN_VALUE = true; + + /** + * Parses the given {@code String} of arguments in the context of the CompleteGoalCommand + * and returns an CompleteGoalCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CompleteGoalCommand parse(String args) throws ParseException { + + Index index; + try { + index = ParserUtil.parseIndex(args); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, CompleteGoalCommand.MESSAGE_USAGE)); + } + + CompleteGoalDescriptor completeGoalDescriptor = new CompleteGoalDescriptor(); + + Completion completion = new Completion(COMPLETED_BOOLEAN_VALUE); + EndDateTime endDateTime = new EndDateTime(properDateTimeFormat(LocalDateTime.now())); + completeGoalDescriptor.setCompletion(completion); + completeGoalDescriptor.setEndDateTime(endDateTime); + + + return new CompleteGoalCommand(index, completeGoalDescriptor); + } +} +``` +###### \java\seedu\address\logic\parser\DateTimeParser.java +``` java +/** + * Contains utility methods used for parsing DateTime in the various *Parser classes. + */ +public class DateTimeParser { + + private static final int BEGIN_INDEX = 6; + /** + * Parses user input String specified{@code args} into LocalDateTime objects + * + * @return Empty Optional if args could not be parsed + * @Disclaimer : The parser used is a NLP API called 'natty' developed by 'Joe Stelmach' + */ + public static Optional nattyDateAndTimeParser(String args) { + if (args == null || args.isEmpty()) { + return Optional.empty(); + } + + Parser parser = new Parser(); + List groups = parser.parse(args); + + //Cannot be parsed + if (groups.size() <= 0) { + return Optional.empty(); + } + + DateGroup dateGroup = (DateGroup) groups.get(0); + if (dateGroup.getDates().size() < 0) { + return Optional.empty(); + } + + Date date = dateGroup.getDates().get(0); + + LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); + return Optional.ofNullable(localDateTime); + } + + /** + * Receives a LocalDateTime and formats the {@code dateTime} + * + * @return a formatted dateTime in String + */ + public static String properDateTimeFormat(LocalDateTime dateTime) { + StringBuilder builder = new StringBuilder(); + int day = dateTime.getDayOfMonth(); + String month = dateTime.getMonth().getDisplayName(TextStyle.FULL, Locale.ENGLISH); + int year = dateTime.getYear(); + int hour = dateTime.getHour(); + int minute = dateTime.getMinute(); + builder.append("Date: ") + .append(day) + .append(" ") + .append(month) + .append(" ") + .append(year) + .append(", Time: ") + .append(String.format("%02d", hour)) + .append(":") + .append(String.format("%02d", minute)); + return builder.toString(); + } + + public static LocalDateTime getLocalDateTimeFromProperDateTime(String properDateTimeString) { + String trimmedArgs = properDateTimeString.trim(); + int size = trimmedArgs.length(); + String stringFormat = properDateTimeString.substring(BEGIN_INDEX, size); + stringFormat = stringFormat.replace(", Time: ", ""); + return nattyDateAndTimeParser(stringFormat).get(); + } + + /** + * Receives a LocalDateTime and formats the {@code dateTime} + * + * @return a formatted dateTime in String + */ + public static String properReminderDateTimeFormat(LocalDateTime dateTime) { + StringBuilder builder = new StringBuilder(); + int day = dateTime.getDayOfMonth(); + String month = dateTime.getMonth().getDisplayName(TextStyle.FULL, Locale.ENGLISH); + int year = dateTime.getYear(); + int hour = dateTime.getHour(); + int minute = dateTime.getMinute(); + builder.append(day) + .append("/") + .append(month) + .append("/") + .append(year) + .append(" ") + .append(String.format("%02d", hour)) + .append(":") + .append(String.format("%02d", minute)); + return builder.toString(); + } + + public static boolean containsDateAndTime(String args) { + return nattyDateAndTimeParser(args).isPresent(); + } + + public static LocalDateTime getLocalDateTimeFromString(String dateString) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + LocalDateTime dateTime = LocalDateTime.parse(dateString, formatter); + return dateTime; + } +} +``` +###### \java\seedu\address\logic\parser\DeleteGoalCommandParser.java +``` java +/** + * Parses input arguments and creates a new DeleteGoalCommand object + */ +public class DeleteGoalCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteGoalCommand + * and returns an DeleteGoalCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteGoalCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteGoalCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteGoalCommand.MESSAGE_USAGE)); + } + } + +} +``` +###### \java\seedu\address\logic\parser\EditCommandParser.java +``` java + /** + * Parses {@code Collection ccas} into a {@code Set} if {@code ccas} is non-empty. + * If {@code ccas} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero ccas. + */ + private Optional> parseCcasForEdit(Collection ccas) throws IllegalValueException { + assert ccas != null; + + if (ccas.isEmpty()) { + return Optional.empty(); + } + Collection ccaSet = ccas.size() == 1 && ccas.contains("") ? Collections.emptySet() : ccas; + return Optional.of(ParserUtil.parseCcas(ccaSet)); + } + +``` +###### \java\seedu\address\logic\parser\EditGoalCommandParser.java +``` java +/** + * Parses input arguments and creates a new EditGoalCommand object + */ +public class EditGoalCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditGoalCommand + * and returns an EditGoalCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public EditGoalCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_GOAL_TEXT, PREFIX_IMPORTANCE); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditGoalCommand.MESSAGE_USAGE)); + } + + EditGoalDescriptor editGoalDescriptor = new EditGoalDescriptor(); + try { + ParserUtil.parseGoalText(argMultimap.getValue(PREFIX_GOAL_TEXT)).ifPresent(editGoalDescriptor::setGoalText); + ParserUtil.parseImportance(argMultimap.getValue(PREFIX_IMPORTANCE)) + .ifPresent(editGoalDescriptor::setImportance); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + + if (!editGoalDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditGoalCommand.MESSAGE_NOT_EDITED); + } + + return new EditGoalCommand(index, editGoalDescriptor); + } +} +``` +###### \java\seedu\address\logic\parser\OngoingGoalCommandParser.java +``` java +/** + * Parses input arguments and creates a new OngoingGoalCommand object + */ +public class OngoingGoalCommandParser implements Parser { + + public static final boolean ONGOING_BOOLEAN_VALUE = false; + /** + * Parses the given {@code String} of arguments in the context of the OngoingGoalCommand + * and returns an OngoingGoalCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public OngoingGoalCommand parse(String args) throws ParseException { + + Index index; + try { + index = ParserUtil.parseIndex(args); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, OngoingGoalCommand.MESSAGE_USAGE)); + } + + OngoingGoalDescriptor ongoingGoalDescriptor = new OngoingGoalDescriptor(); + + Optional empty = Optional.empty(); + Completion completion = new Completion(ONGOING_BOOLEAN_VALUE); + EndDateTime endDateTime = new EndDateTime(""); + ongoingGoalDescriptor.setCompletion(completion); + ongoingGoalDescriptor.setEndDateTime(endDateTime); + + + return new OngoingGoalCommand(index, ongoingGoalDescriptor); + } +} +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String birthday} into an {@code birthday}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code address} is invalid. + */ + public static Birthday parseBirthday(String birthday) throws IllegalValueException { + requireNonNull(birthday); + String trimmedBirthday = birthday.trim(); + if (!Birthday.isValidBirthday(trimmedBirthday)) { + throw new IllegalValueException(Birthday.MESSAGE_BIRTHDAY_CONSTRAINTS); + } + return new Birthday(trimmedBirthday); + } + + /** + * Parses a {@code Optional birthday} into an {@code Optional} if {@code birthday} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseBirthday(Optional birthday) throws IllegalValueException { + requireNonNull(birthday); + return birthday.isPresent() ? Optional.of(parseBirthday(birthday.get())) : Optional.empty(); + } + + /** + * Parses a {@code String levelOfFriendship} into a {@code LevelOfFriendship}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code levelOfFriendship} is invalid. + */ + public static LevelOfFriendship parseLevelOfFriendship(String levelOfFriendship) throws IllegalValueException { + requireNonNull(levelOfFriendship); + String trimmedLevelOfFriendship = levelOfFriendship.trim(); + if (!LevelOfFriendship.isValidLevelOfFriendship(trimmedLevelOfFriendship)) { + throw new IllegalValueException(LevelOfFriendship.MESSAGE_LEVEL_OF_FRIENDSHIP_CONSTRAINTS); + } + return new LevelOfFriendship(trimmedLevelOfFriendship); + } + + /** + * Parses a {@code Optional name} into an {@code Optional} if {@code name} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseLevelOfFriendship(Optional levelOfFriendship) + throws IllegalValueException { + requireNonNull(levelOfFriendship); + return levelOfFriendship.isPresent() ? Optional.of(parseLevelOfFriendship(levelOfFriendship.get())) + : Optional.empty(); + } + + /** + * Parses a {@code String unitNumber} into an {@code UnitNumber}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code unitNumber} is invalid. + */ + public static UnitNumber parseUnitNumber(String unitNumber) throws IllegalValueException { + requireNonNull(unitNumber); + String trimmedUnitNumber = unitNumber.trim(); + if (!UnitNumber.isValidUnitNumber(trimmedUnitNumber)) { + throw new IllegalValueException(UnitNumber.MESSAGE_UNIT_NUMBER_CONSTRAINTS); + } + return new UnitNumber(trimmedUnitNumber); + } + + /** + * Parses a {@code Optional unitNumber} into an {@code Optional} if {@code unitNumber} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseUnitNumber(Optional unitNumber) throws IllegalValueException { + requireNonNull(unitNumber); + return unitNumber.isPresent() ? Optional.of(parseUnitNumber(unitNumber.get())) : Optional.empty(); + } +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String cca} into a {@code Cca} + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code cca} is invalid. + */ + public static Cca parseCca(String cca) throws IllegalValueException { + requireNonNull(cca); + String trimmedCca = cca.trim(); + if (!Cca.isValidCcaName(trimmedCca)) { + throw new IllegalValueException(Cca.MESSAGE_CCA_CONSTRAINTS); + } + return new Cca(trimmedCca); + } + + /** + * Parses {@code Collection ccas} into a {@code Set}. + */ + public static Set parseCcas(Collection ccas) throws IllegalValueException { + requireNonNull(ccas); + final Set ccaSet = new HashSet<>(); + for (String ccaName : ccas) { + ccaSet.add(parseCca(ccaName)); + } + return ccaSet; + } + +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String importance} into an {@code Importance}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code importance} is invalid. + */ + public static Importance parseImportance(String importance) throws IllegalValueException { + requireNonNull(importance); + String trimmedImportance = importance.trim(); + if (!Importance.isValidImportance(trimmedImportance)) { + throw new IllegalValueException(Importance.MESSAGE_IMPORTANCE_CONSTRAINTS); + } + return new Importance(trimmedImportance); + } + + /** + * Parses a {@code Optional importance} into an {@code Optional} if {@code importance} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseImportance(Optional importance) throws IllegalValueException { + requireNonNull(importance); + return importance.isPresent() ? Optional.of(parseImportance(importance.get())) : Optional.empty(); + } + + /** + * Parses a {@code String goalText} into an {@code GoalText}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code goalText} is invalid. + */ + public static GoalText parseGoalText(String goalText) throws IllegalValueException { + requireNonNull(goalText); + String trimmedGoalText = goalText.trim(); + if (!GoalText.isValidGoalText(trimmedGoalText)) { + throw new IllegalValueException(GoalText.MESSAGE_GOAL_TEXT_CONSTRAINTS); + } + return new GoalText(trimmedGoalText); + } + + /** + * Parses a {@code Optional goalText} into an {@code Optional} if {@code goalText} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseGoalText(Optional goalText) throws IllegalValueException { + requireNonNull(goalText); + return goalText.isPresent() ? Optional.of(parseGoalText(goalText.get())) : Optional.empty(); + } + +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String sortField} and checks if it is a valid sortField parameter. + * + * @throws IllegalValueException if specified String is an invalid field. + */ + public static String parseSortGoalField(String sortField) throws IllegalValueException { + String trimmedSortField = sortField.trim(); + switch (trimmedSortField) { + case "importance": + case "completion": + case "startdatetime": + return trimmedSortField; + default: + throw new IllegalValueException(MESSAGE_INVALID_SORT_FIELD); + } + } + + /** + * Parses a {@code Optional sortField} into an {@code Optional} if {@code sortField} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseSortGoalField(Optional sortField) throws IllegalValueException { + requireNonNull(sortField); + return sortField.isPresent() ? Optional.of(parseSortGoalField(sortField.get())) : Optional.empty(); + } + + /** + * Parses a {@code String order} and check if it is a valid order parameter. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code order} is invalid. + */ + public static String parseSortGoalOrder(String order) throws IllegalValueException { + requireNonNull(order); + String trimmedOrder = order.trim(); + switch (trimmedOrder) { + case "ascending": + case "descending": + return trimmedOrder; + default: + throw new IllegalValueException(MESSAGE_INVALID_ORDER_FIELD); + } + } + + /** + * Parses a {@code Optional sortOrder} into an {@code Optional} if {@code sortOrder} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseSortGoalOrder(Optional sortOrder) throws IllegalValueException { + requireNonNull(sortOrder); + return sortOrder.isPresent() ? Optional.of(parseSortGoalOrder(sortOrder.get())) : Optional.empty(); + } +} +``` +###### \java\seedu\address\logic\parser\SortGoalCommandParser.java +``` java +/** + * Parses input arguments and creates a new SortGoalCommand object + */ +public class SortGoalCommandParser implements Parser { + + @Override + public SortGoalCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_SORT_FIELD, PREFIX_SORT_ORDER); + if (!arePrefixesPresent(argMultimap, PREFIX_SORT_FIELD, PREFIX_SORT_ORDER) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortGoalCommand.MESSAGE_USAGE)); + } + try { + String sortField = ParserUtil.parseSortGoalField(argMultimap.getValue(PREFIX_SORT_FIELD)).get(); + String sortOrder = ParserUtil.parseSortGoalOrder(argMultimap.getValue(PREFIX_SORT_ORDER)).get(); + return new SortGoalCommand(sortField, sortOrder); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortGoalCommand.MESSAGE_USAGE)); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} +``` +###### \java\seedu\address\logic\parser\ThemeCommandParser.java +``` java +/** + * Parses input arguments and creates a new ThemeCommand object + */ +public class ThemeCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ThemeCommand + * and returns a ThemeCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ThemeCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ThemeCommand.MESSAGE_USAGE)); + } + if (!isValidThemeColour(trimmedArgs)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ThemeCommand.MESSAGE_INVALID_THEME_COLOUR)); + + } + return new ThemeCommand(trimmedArgs); + } + + /** + * + * @param themeColour + * @return + */ + private boolean isValidThemeColour(String themeColour) { + HashMap themes = getThemeHashMap(); + if (themes.containsKey(themeColour.toLowerCase())) { + return true; + } else { + return false; + } + + } +} + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + public void setCcas(Set ccas) { + this.ccas.setCcas(ccas); } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + public void setGoals(List goals) throws DuplicateGoalException { + this.goals.setGoals(goals); + } + + + public void setReminders(List reminders) throws DuplicateReminderException { + this.reminders.setReminders(reminders); + } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Removes all {@code Ccas}s that are not used by any {@code Person} in this {@code AddressBook}. + */ + private void removeUnusedCcas() { + Set ccasInPersons = persons.asObservableList().stream() + .map(Person::getCcas) + .flatMap(Set::stream) + .collect(Collectors.toSet()); + ccas.setCcas(ccasInPersons); + } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Updates the master cca list to include ccas in {@code person} that are not in the list. + * @return a copy of this {@code person} such that every cca in this person points to a Cca object in the master + * list. + */ + private Person syncWithMasterCcaList(Person person) { + final UniqueCcaList personCcas = new UniqueCcaList(person.getCcas()); + ccas.mergeFrom(personCcas); + + // Create map with values = cca object references in the master list + // used for checking person cca references + final Map masterCcaObjects = new HashMap<>(); + ccas.forEach(cca -> masterCcaObjects.put(cca, cca)); + + // Rebuild the list of person tags to point to the relevant tags in the master tag list. + final Set correctCcaReferences = new HashSet<>(); + personCcas.forEach(cca -> correctCcaReferences.add(masterCcaObjects.get(cca))); + return new Person( + person.getName(), person.getPhone(), person.getBirthday(), + person.getLevelOfFriendship(), person.getUnitNumber(), correctCcaReferences, person.getMeetDate(), + person.getTags()); + } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + public void addCca(Cca cca) throws UniqueCcaList.DuplicateCcaException { + ccas.add(cca); + } + + //// tag-level operations + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Adds a goal to CollegeZone. + * @throws DuplicateGoalException if an equivalent goal already exists. + */ + public void addGoal(Goal g) throws DuplicateGoalException { + goals.add(g); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * @throws GoalNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removeGoal(Goal key) throws GoalNotFoundException { + if (goals.remove(key)) { + return true; + } else { + throw new GoalNotFoundException(); + } + } + + /** + * Replaces the given goal {@code target} in the list with {@code editedGoal}. + * + * @throws DuplicateGoalException if updating the goal's details causes the goal to be equivalent to + * another existing goal in the list. + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + public void updateGoal(Goal target, Goal editedGoal) + throws DuplicateGoalException, GoalNotFoundException { + requireNonNull(editedGoal); + goals.setGoal(target, editedGoal); + } + + /** + * Replaces the given goal {@code target} in the list with {@code editedGoal}. + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + public void updateGoalWithoutParameters(Goal target, Goal editedGoal) throws GoalNotFoundException { + requireNonNull(editedGoal); + goals.setGoalWithoutParameters(target, editedGoal); + } + + /** + * Sorts goal based on the sort field and sort order input. + */ + public void sortGoal(String sortField, String sortOrder) throws EmptyGoalListException { + requireNonNull(sortField); + requireNonNull(sortOrder); + if (goals.getSize() > 0) { + goals.sortGoal(sortField, sortOrder); + } else { + throw new EmptyGoalListException(); + } + } + //// reminder-level operations + +``` +###### \java\seedu\address\model\goal\Completion.java +``` java +/** + * Represents a Goal's completion status in the Goals Page. + */ +public class Completion { + public final String value; + public final boolean hasCompleted; + + /** + * Constructs a {@code Completion}. + * + * @param isCompleted A valid boolean. + */ + public Completion(Boolean isCompleted) { + requireNonNull(isCompleted); + if (isCompleted) { + this.hasCompleted = true; + this.value = "true"; + } else { + this.hasCompleted = false; + this.value = "false"; + } + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Completion // instanceof handles nulls + && this.value.equals(((Completion) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} +``` +###### \java\seedu\address\model\goal\EndDateTime.java +``` java +/** + * Represents a Goal's end date and time in the Goals Page. + * Guarantees: immutable; is valid + */ +public class EndDateTime { + + public final String value; + public final LocalDateTime localDateTimeValue; + /** + * Constructs a {@code EndDateTime}. + * + * @param endDateTime A valid endDateTime number. + */ + public EndDateTime(String endDateTime) { + if (endDateTime.equals("")) { + this.value = ""; + this.localDateTimeValue = null; + } else { + LocalDateTime localEndDateTime = nattyDateAndTimeParser(endDateTime).get(); + this.value = properDateTimeFormat(localEndDateTime); + this.localDateTimeValue = localEndDateTime; + } + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EndDateTime // instanceof handles nulls + && this.value.equals(((EndDateTime) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} +``` +###### \java\seedu\address\model\goal\exceptions\DuplicateGoalException.java +``` java +/** + * Signals that the operation will result in duplicate Goal objects. + */ +public class DuplicateGoalException extends DuplicateDataException { + public DuplicateGoalException() { + super("Operation would result in duplicate goals"); + } +} +``` +###### \java\seedu\address\model\goal\exceptions\EmptyGoalListException.java +``` java +/** + * Signals that the current goal list is empty. + */ +public class EmptyGoalListException extends Exception { +} +``` +###### \java\seedu\address\model\goal\exceptions\GoalNotFoundException.java +``` java +/** + * Signals that the operation is unable to find the specified goal. + */ +public class GoalNotFoundException extends Exception {} +``` +###### \java\seedu\address\model\goal\Goal.java +``` java +/** + * Represents a Goal in the Goals Page. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Goal { + + private final Importance importance; + private final GoalText goalText; + private final StartDateTime startDateTime; + private final EndDateTime endDateTime; + private final Completion completion; + + /** + * Every field must be present and not null. + */ + + public Goal(Importance importance, GoalText goalText, StartDateTime startDateTime, + EndDateTime endDateTime, Completion completion) { + requireAllNonNull(importance, goalText, startDateTime, completion); + this.importance = importance; + this.goalText = goalText; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.completion = completion; + } + + public Importance getImportance() { + return importance; + } + + public GoalText getGoalText() { + return goalText; + } + + public StartDateTime getStartDateTime() { + return startDateTime; + } + + public EndDateTime getEndDateTime() { + return endDateTime; + } + + public Completion getCompletion() { + return completion; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Goal)) { + return false; + } + + Goal otherPerson = (Goal) other; + return otherPerson.getImportance().equals(this.getImportance()) + && otherPerson.getGoalText().equals(this.getGoalText()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(importance, goalText, startDateTime); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(" Importance: ") + .append(getImportance()) + .append(" Goal Text: ") + .append(getGoalText()) + .append(" Start Date Time: ") + .append(getStartDateTime()); + + return builder.toString(); + } + +} +``` +###### \java\seedu\address\model\goal\GoalText.java +``` java +/** + * Represents a Goal's text in the Goals Page. + * Guarantees: immutable; is valid as declared in {@link #isValidGoalText(String)} + */ +public class GoalText { + + + public static final String MESSAGE_GOAL_TEXT_CONSTRAINTS = + "Goal text can be any expression that are not just whitespaces."; + public static final String GOAL_TEXT_VALIDATION_REGEX = "^(?!\\s*$).+"; + public final String value; + + /** + * Constructs a {@code GoalText}. + * + * @param goalText A valid goal text. + */ + public GoalText(String goalText) { + requireNonNull(goalText); + checkArgument(isValidGoalText(goalText), MESSAGE_GOAL_TEXT_CONSTRAINTS); + this.value = goalText; + } + + /** + * Returns true if a given string is a valid goal text. + */ + public static boolean isValidGoalText(String test) { + return test.matches(GOAL_TEXT_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GoalText // instanceof handles nulls + && this.value.equals(((GoalText) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} +``` +###### \java\seedu\address\model\goal\Importance.java +``` java +/** + * Represents a Goal's importance in CollegeZone. + * Guarantees: immutable; is valid as declared in {@link #isValidImportance(String)} + */ +public class Importance implements Comparable { + + + public static final String MESSAGE_IMPORTANCE_CONSTRAINTS = + "Importance should only be a numerical integer value between 1 to 10."; + public static final String IMPORTANCE_VALIDATION_REGEX = "[0-9]+"; + private static final int MINIMUM_IMPORTANCE = 1; + private static final int MAXIMUM_IMPORTANCE = 10; + private static int importanceInIntegerForm; + public final String value; + + /** + * Constructs a {@code Importance}. + * + * @param importance A valid importance. + */ + public Importance(String importance) { + requireNonNull(importance); + checkArgument(isValidImportance(importance), MESSAGE_IMPORTANCE_CONSTRAINTS); + this.value = importance; + } + + /** + * Returns true if a given string is a valid goal importance. + */ + public static boolean isValidImportance(String test) { + return test.matches(IMPORTANCE_VALIDATION_REGEX) && isAnIntegerWithinRange(test); + } + + /** + * Returns true if a given string is an integer and within range of importance. + */ + private static boolean isAnIntegerWithinRange(String test) { + importanceInIntegerForm = Integer.parseInt(test); + if (importanceInIntegerForm >= MINIMUM_IMPORTANCE + && importanceInIntegerForm <= MAXIMUM_IMPORTANCE) { + return true; + } else { + return false; + } + } + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Importance // instanceof handles nulls + && this.value.equals(((Importance) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public int compareTo(Importance importance) { + if ((Integer.valueOf(importance.value)).equals(Integer.valueOf(this.value))) { + return 0; + } else if ((Integer.valueOf(importance.value)) < (Integer.valueOf(this.value))) { + return 1; + } else if ((Integer.valueOf(importance.value)) > (Integer.valueOf(this.value))) { + return -1; + } + return 0; + } +} +``` +###### \java\seedu\address\model\goal\StartDateTime.java +``` java +/** + * Represents a Goal's start date in the address book. + */ +public class StartDateTime implements Comparable { + + public final String value; + public final LocalDateTime localDateTimeValue; + + + /** + * Constructs a {@code StartDateTime}. + * + * @param startDateTime A valid LocalDateTime. + */ + public StartDateTime(LocalDateTime startDateTime) { + requireNonNull(startDateTime); + this.localDateTimeValue = startDateTime; + this.value = properDateTimeFormat(startDateTime); + } + + public StartDateTime(String startDateTimeInString) { + requireNonNull(startDateTimeInString); + this.value = startDateTimeInString; + this.localDateTimeValue = getLocalDateTimeFromProperDateTime(startDateTimeInString); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof StartDateTime // instanceof handles nulls + && this.value.equals(((StartDateTime) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public int compareTo(StartDateTime startDateTime) { + if ((startDateTime.localDateTimeValue).isBefore(this.localDateTimeValue)) { + return 1; + } else if ((startDateTime.localDateTimeValue).isAfter(this.localDateTimeValue)) { + return -1; + } + return 0; + } +} +``` +###### \java\seedu\address\model\goal\UniqueGoalList.java +``` java +/** + * A list of goals that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Goal#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class UniqueGoalList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent goal as the given argument. + */ + public boolean contains(Goal toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a goal to the list. + * + * @throws DuplicateGoalException if the goal to add is a duplicate of an existing goal in the list. + */ + public void add(Goal toAdd) throws DuplicateGoalException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateGoalException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the goal {@code target} in the list with {@code editedGoal}. + * + * @throws DuplicateGoalException if the replacement is equivalent to another existing goal in the list. + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + public void setGoal(Goal target, Goal editedGoal) + throws DuplicateGoalException, GoalNotFoundException { + requireNonNull(editedGoal); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new GoalNotFoundException(); + } + + if (!target.equals(editedGoal) && internalList.contains(editedGoal)) { + throw new DuplicateGoalException(); + } + + internalList.set(index, editedGoal); + } + + /** + * Replaces the goal {@code target} in the list with {@code editedGoal}. + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + public void setGoalWithoutParameters(Goal target, Goal editedGoal) + throws GoalNotFoundException { + requireNonNull(editedGoal); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new GoalNotFoundException(); + } + + internalList.set(index, editedGoal); + } + + /** + * Removes the equivalent goal from the list. + * + * @throws GoalNotFoundException if no such goal could be found in the list. + */ + public boolean remove(Goal toRemove) throws GoalNotFoundException { + requireNonNull(toRemove); + final boolean goalFoundAndDeleted = internalList.remove(toRemove); + if (!goalFoundAndDeleted) { + throw new GoalNotFoundException(); + } + return goalFoundAndDeleted; + } + + public void setGoals(UniqueGoalList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setGoals(List goals) throws DuplicateGoalException { + requireAllNonNull(goals); + final UniqueGoalList replacement = new UniqueGoalList(); + for (final Goal goal : goals) { + replacement.add(goal); + } + setGoals(replacement); + } + + /** + * Returns the size of goal list. + */ + public int getSize() { + return internalList.size(); + } + + /** + * Sort goals internal list using comparator + * @param sortField + */ + public void sortGoal(String sortField, String sortOrder) throws EmptyGoalListException { + String sortFieldAndOrder = sortField + " " + sortOrder; + switch (sortFieldAndOrder) { + case "importance ascending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) ->goalA.getImportance() + .compareTo(goalB.getImportance())); + break; + case "importance descending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) ->goalB.getImportance() + .compareTo(goalA.getImportance())); + break; + case "completion ascending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) -> new Boolean(goalA.getCompletion().hasCompleted) + .compareTo(goalB.getCompletion().hasCompleted)); + break; + case "completion descending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) -> new Boolean(goalB.getCompletion().hasCompleted) + .compareTo(goalA.getCompletion().hasCompleted)); + break; + case "startdatetime ascending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) ->goalA.getStartDateTime() + .compareTo(goalB.getStartDateTime())); + break; + case "startdatetime descending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) ->goalB.getStartDateTime() + .compareTo(goalA.getStartDateTime())); + break; + + default: + break; + } + } + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueGoalList // instanceof handles nulls + && this.internalList.equals(((UniqueGoalList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} +``` +###### \java\seedu\address\model\Model.java +``` java + /** Adds the given goal */ + void addGoal(Goal goal) throws DuplicateGoalException; + + /** Returns an unmodifiable view of the filtered goal list */ + ObservableList getFilteredGoalList(); + + /** + * Replaces the given goal {@code target} with {@code editedGoal}. + * + * @throws DuplicateGoalException if updating the goal's details causes the goal to be equivalent to + * another existing goal in the list. + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + void updateGoal(Goal target, Goal editedGoal) + throws DuplicateGoalException, GoalNotFoundException; + + /** + * Updates the filter of the filtered goal list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredGoalList(Predicate predicate); + + /** Deletes the given goal. */ + void deleteGoal(Goal target) throws GoalNotFoundException; + + /** + * Replaces the given goal {@code target} with {@code updateGoal}. + * + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + void updateGoalWithoutParameters(Goal target, Goal editedGoal) throws GoalNotFoundException; + + /** + * Sort the goal based on sortType + */ + void sortGoal(String sortType, String sortOrder) throws EmptyGoalListException; + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public void addGoal(Goal goal) throws DuplicateGoalException { + addressBook.addGoal(goal); + updateFilteredGoalList(PREDICATE_SHOW_ALL_GOALS); + indicateAddressBookChanged(); + } + + @Override + public ObservableList getFilteredGoalList() { + return FXCollections.unmodifiableObservableList(filteredGoals); + } + + @Override + public void updateFilteredGoalList(Predicate predicate) { + requireNonNull(predicate); + filteredGoals.setPredicate(predicate); + } + + @Override + public synchronized void deleteGoal(Goal target) throws GoalNotFoundException { + addressBook.removeGoal(target); + indicateAddressBookChanged(); + } + + @Override + public void updateGoal(Goal target, Goal editedGoal) + throws DuplicateGoalException, GoalNotFoundException { + requireAllNonNull(target, editedGoal); + + addressBook.updateGoal(target, editedGoal); + indicateAddressBookChanged(); + } + + @Override + public void updateGoalWithoutParameters(Goal target, Goal editedGoal) + throws GoalNotFoundException { + requireAllNonNull(target, editedGoal); + + addressBook.updateGoalWithoutParameters(target, editedGoal); + indicateAddressBookChanged(); + } + + @Override + public void sortGoal(String sortGoalType, String sortGoalOrder) throws EmptyGoalListException { + requireAllNonNull(sortGoalType, sortGoalOrder); + addressBook.sortGoal(sortGoalType, sortGoalOrder); + indicateAddressBookChanged(); + } + +``` +###### \java\seedu\address\model\person\Birthday.java +``` java +/** + * Represents a Person's birthday in CollegeZone. + * Guarantees: immutable; is valid as declared in {@link #isValidBirthday(String)} + */ +public class Birthday { + + public static final String MESSAGE_BIRTHDAY_CONSTRAINTS = "Person birthday should be a valid date and cannot " + + "be later than today's date."; + public static final String BIRTHDAY_VALIDATION_REGEX = + "^(((0[1-9]|[12]\\d|3[01])\\/(0[13578]|1[02])\\/((19|[2-9]\\d)\\d{2}))|((0[1-9]|[12]\\d|30)" + + "\\/(0[13456789]|1[012])\\/((19|[2-9]\\d)\\d{2}))|((0[1-9]|1\\d|2[0-8])\\/02\\/((19|[2-9]\\d)" + + "\\d{2}))|(29\\/02\\/((1[6-9]|[2-9]\\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|" + + "[3579][26])00))))$"; + + public final String value; + + /** + * Constructs an {@code Birthday}. + * + * @param birthday A valid birthday. + */ + public Birthday(String birthday) { + requireNonNull(birthday); + checkArgument(isValidBirthday(birthday), MESSAGE_BIRTHDAY_CONSTRAINTS); + this.value = birthday; + } + + /** + * Returns if a given string is a valid person birthday (before current date). + */ + public static boolean isValidBirthday(String test) { + LocalDateTime birthdayLocalDateTime = null; + LocalDateTime currentLocalDateTime = LocalDateTime.now(); + if (test.matches(BIRTHDAY_VALIDATION_REGEX)) { + String birthdayInDifferentFormat = getDifferentBirthdayFormat(test); + birthdayLocalDateTime = nattyDateAndTimeParser(birthdayInDifferentFormat).get(); + } else { + return false; + } + return isBeforeCurrentDate(birthdayLocalDateTime, currentLocalDateTime); + } + + /** + * Takes in @param birthdayLocalDateTime and @param currentLocalDateTime and checks if 1st parameter is later + * than the second parameter + * @return boolean + */ + private static boolean isBeforeCurrentDate(LocalDateTime birthdayLocalDateTime, + LocalDateTime currentLocalDateTime) { + if (birthdayLocalDateTime.isBefore(currentLocalDateTime)) { + return true; + } else { + return false; + } + } + /** + * + * Takes in @param date in dd/mm/yyyy format + * @return birthday string in mm/dd/yyyy format + */ + public static String getDifferentBirthdayFormat(String date) { + String day = date.substring(0, 2); + String month = date.substring(3, 5); + String year = date.substring(6, 10); + StringBuilder builder = new StringBuilder(); + builder.append(month) + .append("/") + .append(day) + .append("/") + .append(year); + return builder.toString(); + } + +``` +###### \java\seedu\address\model\person\Birthday.java +``` java + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Birthday // instanceof handles nulls + && this.value.equals(((Birthday) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} +``` +###### \java\seedu\address\model\person\Cca.java +``` java +/** + * Represents a Person's CCAs in CollegeZone. + * Guarantees: immutable; is valid as declared in {@link #isValidCcaName(String)} + */ +public class Cca { + + public static final String MESSAGE_CCA_CONSTRAINTS = "CCAs should be in alphanumeric"; + public static final String CCA_VALIDATION_REGEX = "\\s*\\p{Alnum}[\\p{Alnum}\\s]*"; + public final String ccaName; + + /** + * Constructs a {@code CCA}. + * + * @param ccaName A valid CCA. + */ + public Cca(String ccaName) { + requireNonNull(ccaName); + checkArgument(isValidCcaName(ccaName), MESSAGE_CCA_CONSTRAINTS); + this.ccaName = ccaName; + } + + /** + * Returns true if a given string is a valid CCA name. + */ + public static boolean isValidCcaName(String test) { + return test.matches(CCA_VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Cca // instanceof handles nulls + && this.ccaName.equals(((Cca) other).ccaName)); // state check + } + + @Override + public int hashCode() { + return ccaName.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return '[' + ccaName + ']'; + } + +} +``` +###### \java\seedu\address\model\person\LevelOfFriendship.java +``` java +/** + * Represents a Person's Level of Friendship in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidLevelOfFriendship(String)} + */ +public class LevelOfFriendship { + + public static final String MESSAGE_LEVEL_OF_FRIENDSHIP_CONSTRAINTS = + "Level of Friendship should only be a numerical integer value between 1 to 10"; + public static final String LEVEL_OF_FRIENDSHIP_VALIDATION_REGEX = "[0-9]+"; + private static final int MINIMUM_LEVEL_OF_FRIENDSHIP = 1; + private static final int MAXIMUM_LEVEL_OF_FRIENDSHIP = 10; + private static int levelOfFriendshipInIntegerForm; + public final String value; + + /** + * * Constructs an {@code LevelOfFriendship}. + * + * @param levelOfFriendship A valid level of friendship number. + */ + public LevelOfFriendship(String levelOfFriendship) { + requireNonNull(levelOfFriendship); + checkArgument(isValidLevelOfFriendship(levelOfFriendship), MESSAGE_LEVEL_OF_FRIENDSHIP_CONSTRAINTS); + this.value = levelOfFriendship; + } + + /** + * Returns true if a given string is a valid person level of friendship. + */ + public static boolean isValidLevelOfFriendship(String test) { + + return test.matches(LEVEL_OF_FRIENDSHIP_VALIDATION_REGEX) && isAnIntegerWithinRange(test); + } + + /** + * Returns true if a given string is an integer and within range of level of friendship. + */ + private static boolean isAnIntegerWithinRange(String test) { + levelOfFriendshipInIntegerForm = Integer.parseInt(test); + if (levelOfFriendshipInIntegerForm >= MINIMUM_LEVEL_OF_FRIENDSHIP + && levelOfFriendshipInIntegerForm <= MAXIMUM_LEVEL_OF_FRIENDSHIP) { + return true; + } else { + return false; + } + } + + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LevelOfFriendship // instanceof handles nulls + && this.value.equals(((LevelOfFriendship) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} +``` +###### \java\seedu\address\model\person\UniqueCcaList.java +``` java +/** + * A list of ccas that enforces no nulls and uniqueness between its elements. + * + * Supports minimal set of list operations for the app's features. + * + * @see Cca#equals(Object) + */ +public class UniqueCcaList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Constructs empty CcaList. + */ + public UniqueCcaList() {} + + /** + * Creates a UniqueCcaList using given ccas. + * Enforces no nulls. + */ + public UniqueCcaList(Set ccas) { + requireAllNonNull(ccas); + internalList.addAll(ccas); + + assert CollectionUtil.elementsAreUnique(internalList); + } + + /** + * Returns all ccas in this list as a Set. + * This set is mutable and change-insulated against the internal list. + */ + public Set toSet() { + assert CollectionUtil.elementsAreUnique(internalList); + return new HashSet<>(internalList); + } + + /** + * Replaces the Ccas in this list with those in the argument cca list. + */ + public void setCcas(Set ccas) { + requireAllNonNull(ccas); + internalList.setAll(ccas); + assert CollectionUtil.elementsAreUnique(internalList); + } + + /** + * Ensures every tag in the argument list exists in this object. + */ + public void mergeFrom(UniqueCcaList from) { + final Set alreadyInside = this.toSet(); + from.internalList.stream() + .filter(cca -> !alreadyInside.contains(cca)) + .forEach(internalList::add); + + assert CollectionUtil.elementsAreUnique(internalList); + } + + /** + * Returns true if the list contains an equivalent Cca as the given argument. + */ + public boolean contains(Cca toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a Cca to the list. + * + * @throws DuplicateCcaException if the Cca to add is a duplicate of an existing Cca in the list. + */ + public void add(Cca toAdd) throws DuplicateCcaException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateCcaException(); + } + internalList.add(toAdd); + + assert CollectionUtil.elementsAreUnique(internalList); + } + + @Override + public Iterator iterator() { + assert CollectionUtil.elementsAreUnique(internalList); + return internalList.iterator(); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + assert CollectionUtil.elementsAreUnique(internalList); + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public boolean equals(Object other) { + assert CollectionUtil.elementsAreUnique(internalList); + return other == this // short circuit if same object + || (other instanceof UniqueCcaList // instanceof handles nulls + && this.internalList.equals(((UniqueCcaList) other).internalList)); + } + + /** + * Returns true if the element in this list is equal to the elements in {@code other}. + * The elements do not have to be in the same order. + */ + public boolean equalsOrderInsensitive(UniqueCcaList other) { + assert CollectionUtil.elementsAreUnique(internalList); + assert CollectionUtil.elementsAreUnique(other.internalList); + return this == other || new HashSet<>(this.internalList).equals(new HashSet<>(other.internalList)); + } + + @Override + public int hashCode() { + assert CollectionUtil.elementsAreUnique(internalList); + return internalList.hashCode(); + } + + /** + * Signals that an operation would have violated the 'no duplicates' property of the list. + */ + public static class DuplicateCcaException extends DuplicateDataException { + protected DuplicateCcaException() { + super("Operation would result in duplicate ccas"); + } + } + +} +``` +###### \java\seedu\address\model\person\UnitNumber.java +``` java + /** + * * Constructs an {@code UnitNumber}. + * + * @param unitNumber A valid unit number. + */ + public UnitNumber(String unitNumber) { + requireNonNull(unitNumber); + checkArgument(isValidUnitNumber(unitNumber), MESSAGE_UNIT_NUMBER_CONSTRAINTS); + this.value = unitNumber; + } + + /** + * Returns true if a given string is a valid unit number. + */ + public static boolean isValidUnitNumber(String test) { + + return test.matches(UNIT_NUMBER_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + +``` +###### \java\seedu\address\model\person\UnitNumber.java +``` java + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UnitNumber // instanceof handles nulls + && this.value.equals(((UnitNumber) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} +``` +###### \java\seedu\address\model\ReadOnlyAddressBook.java +``` java + /** + * Returns an unmodifiable view of the ccas list. + * This list will not contain any duplicate ccas. + */ + ObservableList getCcaList(); + +``` +###### \java\seedu\address\model\ReadOnlyAddressBook.java +``` java + /** + * Returns an unmodifiable view of the goals list. + * This list will not contain any duplicate goals. + */ + ObservableList getGoalList(); +``` +###### \java\seedu\address\model\ThemeColourUtil.java +``` java +/** + * Contains utility methods for ThemeColour. + */ +public class ThemeColourUtil { + + private static final HashMap themes; + + static { + themes = new HashMap<>(); + themes.put("light", "view/LightTheme.css"); + themes.put("bubblegum", "view/BubblegumTheme.css"); + themes.put("dark", "view/DarkTheme.css"); + } + + public static HashMap getThemeHashMap() { + return themes; + } +} +``` +###### \java\seedu\address\model\util\SampleCollegeZone.java +``` java +/** + * Contains method to get a sample CollegeZone data + */ +public class SampleCollegeZone { + + public static ReadOnlyAddressBook getSampleCollegeZone() { + AddressBook sampleCz = new AddressBook(); + try { + for (Person samplePerson : getSamplePersons()) { + sampleCz.addPerson(samplePerson); + } + } catch (DuplicatePersonException e) { + throw new AssertionError("sample data cannot contain duplicate persons", e); + } + try { + for (Goal sampleGoal : getSampleGoals()) { + sampleCz.addGoal(sampleGoal); + } + } catch (DuplicateGoalException e) { + throw new AssertionError("sample data cannot contain duplicate goals", e); + } + try { + for (Reminder sampleReminder : getSampleReminders()) { + sampleCz.addReminder(sampleReminder); + } + } catch (DuplicateReminderException e) { + throw new AssertionError("sample data cannot contain duplicate reminders", e); + } + return sampleCz; + } +} +``` +###### \java\seedu\address\model\util\SampleDataUtil.java +``` java +/** + * Contains utility methods for populating {@code CollegeZone} with sample data. + */ +public class SampleDataUtil { + + public static final Meet MEET_DATE = new Meet("15/04/2018"); + + public static Person[] getSamplePersons() { + return new Person[] { + new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Birthday("01/01/1997"), + new LevelOfFriendship("5"), new UnitNumber("#06-40"), getCcaSet("Basketball"), + MEET_DATE, getTagSet("friends", "RA")), + new Person(new Name("Bernice Yu"), new Phone("99272758"), new Birthday("21/02/1990"), + new LevelOfFriendship("9"), new UnitNumber("#07-18"), getCcaSet(), + new Meet("15/05/2018"), getTagSet("colleagues", "friends")), + new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Birthday("05/09/1980"), + new LevelOfFriendship("1"), new UnitNumber("#11-04"), getCcaSet("Swimming"), + new Meet("15/05/2018"), getTagSet("neighbours")), + new Person(new Name("David Li"), new Phone("91031282"), new Birthday("20/02/1995"), + new LevelOfFriendship("6"), new UnitNumber("#16-43"), getCcaSet(), + new Meet("16/04/2018"), getTagSet("family")), + new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Birthday("01/01/1999"), + new LevelOfFriendship("7"), new UnitNumber("#16-41"), getCcaSet(), + new Meet("17/04/2018"), getTagSet("classmates")), + new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Birthday("02/04/1995"), + new LevelOfFriendship("10"), new UnitNumber("#6-43"), getCcaSet("Computing club", "Anime Club"), + new Meet("18/04/2018"), getTagSet("colleagues")), + new Person(new Name("James Mee"), new Phone("98887555"), new Birthday("22/08/1992"), + new LevelOfFriendship("1"), new UnitNumber("#06-40"), getCcaSet("Tennis"), + new Meet("19/04/2018"), getTagSet("RA")), + new Person(new Name("Jane Ray"), new Phone("93336444"), new Birthday("25/09/1991"), + new LevelOfFriendship("1"), new UnitNumber("#07-40"), getCcaSet("Chess Club"), + new Meet("20/04/2018"), getTagSet("RA")), + new Person(new Name("Deborah Low"), new Phone("91162930"), new Birthday("24/05/1997"), + new LevelOfFriendship("9"), new UnitNumber("#10-24"), getCcaSet("Aerobics Club"), + new Meet("21/04/2018"), getTagSet("colleagues")), + new Person(new Name("Royce Lew"), new Phone("93265932"), new Birthday("10/04/1996"), + new LevelOfFriendship("5"), new UnitNumber("#02-021"), getCcaSet(), + new Meet("22/04/2018"), getTagSet("boyfriend")), + new Person(new Name("Kaden Yeo"), new Phone("82350332"), new Birthday("28/03/2001"), + new LevelOfFriendship("6"), new UnitNumber("#6-20"), getCcaSet("shooting"), + new Meet("23/04/2018"), getTagSet("friends")), + new Person(new Name("Matthew Chiang"), new Phone("92624417"), new Birthday("02/04/1995"), + new LevelOfFriendship("4"), new UnitNumber("#20-43"), getCcaSet("Anime Club"), + new Meet("24/04/2018"), getTagSet("classmate")), + new Person(new Name("Loh Sin Yuen"), new Phone("92624417"), new Birthday("02/05/1995"), + new LevelOfFriendship("10"), new UnitNumber("#03-63"), getCcaSet("dance"), + new Meet("25/04/2018"), getTagSet("schoolmate")), + new Person(new Name("Florence Chiang"), new Phone("92624417"), new Birthday("02/06/1995"), + new LevelOfFriendship("10"), new UnitNumber("#6-97"), getCcaSet("volleyball"), + new Meet("26/04/2018"), getTagSet("bff")), + new Person(new Name("Daniel Low"), new Phone("92624417"), new Birthday("12/04/1995"), + new LevelOfFriendship("1"), new UnitNumber("#7-473"), getCcaSet("Muay Thai"), + new Meet("27/04/2018"), getTagSet("cousin")), + new Person(new Name("Rachel Lee Yan Ling"), new Phone("92624417"), new Birthday("23/04/1995"), + new LevelOfFriendship("3"), new UnitNumber("#6-69"), getCcaSet("Computing club", "Anime Club"), + new Meet("28/04/2018"), getTagSet("cousin")), + new Person(new Name("Sarah tan"), new Phone("92624417"), new Birthday("27/04/1999"), + new LevelOfFriendship("2"), new UnitNumber("#8-43"), getCcaSet("Computing club", "Anime Club"), + new Meet("28/04/2018"), getTagSet()), + new Person(new Name("Amanda Soh"), new Phone("92624417"), new Birthday("02/12/1995"), + new LevelOfFriendship("1"), new UnitNumber("#24-579"), getCcaSet("Computing club", "Anime Club"), + new Meet("17/06/2018"), getTagSet("exgirlfriend")), + new Person(new Name("Marlene Koh"), new Phone("92624417"), new Birthday("02/07/1997"), + new LevelOfFriendship("10"), new UnitNumber("#02-222"), getCcaSet("Pool"), + new Meet("17/07/2018"), getTagSet("closefriend")), + new Person(new Name("Johnny Depp"), new Phone("92624417"), new Birthday("02/12/1994"), + new LevelOfFriendship("2"), new UnitNumber("#01-346"), getCcaSet("Pool"), + new Meet("17/08/2018"), getTagSet("malafriend")), + new Person(new Name("Aditya"), new Phone("92624417"), new Birthday("02/04/1998"), + new LevelOfFriendship("3"), new UnitNumber("#6-43"), getCcaSet(), + new Meet("17/09/2018"), getTagSet("malafriend")), + new Person(new Name("Fuad"), new Phone("92624417"), new Birthday("20/04/1995"), + new LevelOfFriendship("9"), new UnitNumber("#6-43"), getCcaSet("Floorball"), + new Meet("17/04/2018"), getTagSet("colleagues")) + }; + } + + public static ReadOnlyAddressBook getSampleAddressBook() { + try { + AddressBook sampleAb = new AddressBook(); + for (Person samplePerson : getSamplePersons()) { + sampleAb.addPerson(samplePerson); + } + return sampleAb; + } catch (DuplicatePersonException e) { + throw new AssertionError("sample data cannot contain duplicate persons", e); + } + } + + /** + * Returns a cca set containing the list of strings given. + */ + public static Set getCcaSet(String... strings) { + HashSet ccas = new HashSet<>(); + for (String s : strings) { + ccas.add(new Cca(s)); + } + + return ccas; + } + + /** + * Returns a tag set containing the list of strings given. + */ + public static Set getTagSet(String... strings) { + HashSet tags = new HashSet<>(); + for (String s : strings) { + tags.add(new Tag(s)); + } + + return tags; + } + +} +``` +###### \java\seedu\address\model\util\SampleGoalDataUtil.java +``` java +/** + * Contains utility methods for populating {@code CollegeZone} with sample data. + */ +public class SampleGoalDataUtil { + + public static final EndDateTime EMPTY_END_DATE_TIME = new EndDateTime(""); + + public static Goal[] getSampleGoals() { + return new Goal[] { + + new Goal(new Importance("1"), new GoalText("finish cs2103"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("2"), new GoalText("no"), + new StartDateTime(getLocalDateTimeFromString("2018-04-08 12:12")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("3"), new GoalText("grow taller"), + new StartDateTime(getLocalDateTimeFromString("1997-04-08 12:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("3"), new GoalText("finish cs2105 assignments"), + new StartDateTime(getLocalDateTimeFromString("2018-04-08 10:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("1"), new GoalText("learning digital art"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:39")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("2"), new GoalText("finish cs2103!!!!"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("10"), new GoalText("finish cs2103!!!!"), + new StartDateTime(getLocalDateTimeFromString("2018-03-18 08:30")), + new EndDateTime("03/04/2018 12:30"), new Completion(true)), + new Goal(new Importance("6"), new GoalText("lose 0.5kg by this week"), + new StartDateTime(getLocalDateTimeFromString("2018-04-06 19:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("10"), new GoalText("Find love <3"), + new StartDateTime(getLocalDateTimeFromString("2014-04-08 20:30")), + new EndDateTime("02/02/2018 12:30"), new Completion(true)), + new Goal(new Importance("7"), new GoalText("water plants"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("5"), new GoalText("buy dog food"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("4"), new GoalText("Take the stairs more often!"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + new EndDateTime("04/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("10"), new GoalText("Eat PGP MALA once every week"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + new EndDateTime("07/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("7"), new GoalText("Make more friends in uni"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:45")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("9"), new GoalText("Go CCA regularly"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 13:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("9"), new GoalText("Drink 8 cups of water everyday"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 01:59")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("8"), new GoalText("Get A for CS2105"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 02:30")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("8"), new GoalText("Get A- for GEH1036"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 03:30")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("10"), new GoalText("Aim to increase CAP by 0.2 by the end of this semester"), + new StartDateTime(getLocalDateTimeFromString("2017-02-18 12:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("4"), new GoalText("Do 50 squats EVERYDAY"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + }; + } + + public static ReadOnlyAddressBook getSampleGoalAddressBook() { + try { + AddressBook sampleAb = new AddressBook(); + for (Goal sampleGoal : getSampleGoals()) { + sampleAb.addGoal(sampleGoal); + } + return sampleAb; + } catch (DuplicateGoalException e) { + throw new AssertionError("sample data cannot contain duplicate goals", e); + } + } +} +``` +###### \java\seedu\address\storage\XmlAdaptedCca.java +``` java +/** + * JAXB-friendly adapted version of the Cca. + */ +public class XmlAdaptedCca { + + @XmlValue + private String ccaName; + + /** + * Constructs an XmlAdaptedCca. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedCca() {} + + /** + * Constructs a {@code XmlAdaptedCca} with the given {@code ccaName}. + */ + public XmlAdaptedCca(String ccaName) { + this.ccaName = ccaName; + } + + /** + * Converts a given Tag into this class for JAXB use. + * + * @param source future changes to this will not affect the created + */ + public XmlAdaptedCca(Cca source) { + ccaName = source.ccaName; + } + + /** + * Converts this jaxb-friendly adapted cca object into the model's Cca object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person + */ + public Cca toModelType() throws IllegalValueException { + if (!Cca.isValidCcaName(ccaName)) { + throw new IllegalValueException(Cca.MESSAGE_CCA_CONSTRAINTS); + } + return new Cca(ccaName); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedCca)) { + return false; + } + + return ccaName.equals(((XmlAdaptedCca) other).ccaName); + } +} +``` +###### \java\seedu\address\storage\XmlAdaptedGoal.java +``` java +/** + * JAXB-friendly version of the Goal. + */ +public class XmlAdaptedGoal { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Goal's %s field is missing!"; + + @XmlElement(required = true) + private String importance; + @XmlElement(required = true) + private String goalText; + @XmlElement(required = true) + private String startDateTime; + @XmlElement(required = true) + private String endDateTime; + @XmlElement(required = true) + private String completion; + + /** + * Constructs an XmlAdaptedGoal. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedGoal() {} + + /** + * Constructs an {@code XmlAdaptedGoal} with the given goal details. + */ + public XmlAdaptedGoal(String importance, String goalText, String startDateTime, String endDateTime, + String completion) { + this.importance = importance; + this.goalText = goalText; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.completion = completion; + } + + /** + * Converts a given Goal into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedGoal + */ + public XmlAdaptedGoal(Goal source) { + importance = source.getImportance().value; + goalText = source.getGoalText().value; + startDateTime = source.getStartDateTime().value; + endDateTime = source.getEndDateTime().value; + completion = source.getCompletion().value; + } + + /** + * Converts this jaxb-friendly adapted goal object into the model's Goal object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted goal + */ + public Goal toModelType() throws IllegalValueException { + + if (this.importance == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Importance.class.getSimpleName())); + } + if (!Importance.isValidImportance(this.importance)) { + throw new IllegalValueException(Importance.MESSAGE_IMPORTANCE_CONSTRAINTS); + } + final Importance importance = new Importance(this.importance); + + if (this.goalText == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + GoalText.class.getSimpleName())); + } + if (!GoalText.isValidGoalText(this.goalText)) { + throw new IllegalValueException(GoalText.MESSAGE_GOAL_TEXT_CONSTRAINTS); + } + final GoalText goalText = new GoalText(this.goalText); + + if (this.startDateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + StartDateTime.class.getSimpleName())); + } + + final StartDateTime startDateTime = new StartDateTime(this.startDateTime); + + if (this.endDateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, EndDateTime + .class.getSimpleName())); + } + + final EndDateTime endDateTime = new EndDateTime(this.endDateTime); + + if (this.completion == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Completion.class.getSimpleName())); + } + + final Completion completion = new Completion((this.completion.equals("true"))); + return new Goal(importance, goalText, startDateTime, endDateTime, completion); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedGoal)) { + return false; + } + + XmlAdaptedGoal otherGoal = (XmlAdaptedGoal) other; + return Objects.equals(importance, otherGoal.importance) + && Objects.equals(goalText, otherGoal.goalText) + && Objects.equals(startDateTime, otherGoal.startDateTime) + && Objects.equals(endDateTime, otherGoal.endDateTime) + && Objects.equals(completion, otherGoal.completion); + } +} + +``` +###### \java\seedu\address\storage\XmlAdaptedPerson.java +``` java + final List personCcas = new ArrayList<>(); + for (XmlAdaptedCca cca : ccas) { + personCcas.add(cca.toModelType()); + } +``` +###### \java\seedu\address\storage\XmlAdaptedPerson.java +``` java + if (this.birthday == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Birthday.class.getSimpleName())); + } + if (!Birthday.isValidBirthday(this.birthday)) { + throw new IllegalValueException(Birthday.MESSAGE_BIRTHDAY_CONSTRAINTS); + } + final Birthday birthday = new Birthday(this.birthday); + + if (this.levelOfFriendship == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, LevelOfFriendship + .class.getSimpleName())); + } + if (!LevelOfFriendship.isValidLevelOfFriendship(this.levelOfFriendship)) { + throw new IllegalValueException(LevelOfFriendship.MESSAGE_LEVEL_OF_FRIENDSHIP_CONSTRAINTS); + } + final LevelOfFriendship levelOfFriendship = new LevelOfFriendship(this.levelOfFriendship); + +``` +###### \java\seedu\address\storage\XmlAdaptedPerson.java +``` java + if (this.unitNumber == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + UnitNumber.class.getSimpleName())); + } + if (!UnitNumber.isValidUnitNumber(this.unitNumber)) { + throw new IllegalValueException(UnitNumber.MESSAGE_UNIT_NUMBER_CONSTRAINTS); + } + final UnitNumber unitNumber = new UnitNumber(this.unitNumber); + final Set ccas = new HashSet<>(personCcas); + +``` +###### \java\seedu\address\ui\GoalCard.java +``` java +/** + * An UI component that displays information of a {@code Goal}. + */ +public class GoalCard extends UiPart { + private static final int NOT_COMPLETED_COLOUR_STYLE = 0; + private static final int COMPLETED_COLOUR_STYLE = 1; + private static final String FXML = "GoalListCard.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. + */ + + public final Goal goal; + + @FXML + private HBox goalCardPane; + @FXML + private Label goalText; + @FXML + private Label id; + @FXML + private FlowPane importance; + @FXML + private Label startDateTime; + @FXML + private Label endDateTime; + @FXML + private FlowPane completion; + + + public GoalCard(Goal goal, int displayedIndex) { + super(FXML); + this.goal = goal; + id.setText(displayedIndex + ". "); + goalText.setText(goal.getGoalText().value); + initImportance(goal); + startDateTime.setText("Start " + goal.getStartDateTime().value); + if (goal.getEndDateTime().value.equals("")) { + endDateTime.setText(goal.getEndDateTime().value); + } else { + endDateTime.setText("End " + goal.getEndDateTime().value); + } + initCompletion(goal); + } + + /** + * Creates the completion label for {@code goal}. + */ + private void initCompletion(Goal goal) { + String trueOrFalseString = goal.getCompletion().value; + if (trueOrFalseString.equals("true")) { + Image completedImage = AppUtil.getImage("/images/completedImage.png"); + ImageView completedImageView = new ImageView(completedImage); + completedImageView.setFitHeight(30); + completedImageView.setFitWidth(30); + Label completionLabel = new Label("Completed", completedImageView); + completion.getChildren().add(completionLabel); + } else { + Image ongoingImage = AppUtil.getImage("/images/ongoingImage.png"); + ImageView ongoingImageView = new ImageView(ongoingImage); + ongoingImageView.setFitHeight(27); + ongoingImageView.setFitWidth(27); + Label completionLabel = new Label("Ongoing", ongoingImageView); + completion.getChildren().add(completionLabel); + } + } + + /** + * Creates the importance label for {@code goal}. + */ + private void initImportance(Goal goal) { + String starValue = changeImportanceToStar(goal.getImportance().value); + Label importanceLabel = new Label(starValue); + importanceLabel.getStyleClass().add("yellow"); + importance.getChildren().add(importanceLabel); + } + + /** + * Takes in @param value representing the importance value + * @return a number of star string. + */ + public static String changeImportanceToStar(String value) { + int intValue = Integer.parseInt(value); + String starString = ""; + for (int i = 0; i < intValue; i++) { + starString = starString + '\u2605' + " "; + } + return starString; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof GoalCard)) { + return false; + } + + // state check + GoalCard card = (GoalCard) other; + return id.getText().equals(card.id.getText()) + && goal.equals(card.goal); + } +} +``` +###### \java\seedu\address\ui\MainWindow.java +``` java + /** + * Calculation of percentage of goal completed + * @return + */ + private int calculateGoalCompletion() { + int totalGoal = logic.getFilteredGoalList().size(); + int totalGoalCompleted = 0; + String completionStatus; + for (int i = 0; i < totalGoal; i++) { + completionStatus = logic.getFilteredGoalList().get(i).getCompletion().value; + totalGoalCompleted += isCompletedGoal(completionStatus); + } + int percentageGoalCompletion = (int) (((float) totalGoalCompleted / totalGoal) * PERCENTAGE_KEY_NUMBER); + return percentageGoalCompletion; + } + + /** + * @param completionStatus gives a String that should be either "true" or "false", indicating if the goal is + * completed. + * @return true or false + */ + private int isCompletedGoal(String completionStatus) { + int valueToAdd; + if (completionStatus.equals("true")) { + valueToAdd = 1; + } else { + valueToAdd = 0; + } + return valueToAdd; + } + + private void setThemeColour() { + setThemeColour(DEFAULT_THEME_COLOUR); + } + + private void setThemeColour(String themeColour) { + primaryStage.getScene().getStylesheets().add(EXTENSIONS_STYLESHEET); + primaryStage.getScene().getStylesheets().add(themeHashMap.get(themeColour)); + } + + private void changeThemeColour() { + primaryStage.getScene().getStylesheets().clear(); + setThemeColour(themeColour); + } + + @Subscribe + private void handleChangeThemeEvent(ThemeSwitchRequestEvent event) { + themeColour = event.themeToChangeTo; + Platform.runLater( + this::changeThemeColour + ); + } +} +``` +###### \java\seedu\address\ui\PersonCard.java +``` java + /** + * Returns the color style for {@code tagName}'s label. + */ + private String getTagColorStyleFor(String tagName) { + // we use the hash code of the tag name to generate a random color, so that the color remain consistent + // between diffe 11rent runs of the program while still making it random enough between tags. + return TAG_COLOR_STYLES[Math.abs(tagName.hashCode()) % TAG_COLOR_STYLES.length]; + } + + /** + * Creates the tag labels for {@code person}. + */ + private void initTags(Person person) { + person.getTags().forEach(tag -> { + Label tagLabel = new Label(tag.tagName); + tagLabel.getStyleClass().add(getTagColorStyleFor(tag.tagName)); + tags.getChildren().add(tagLabel); + }); + } + + private String getCcasInString(Set ccas) { + String ccasValue = ""; + Iterator iterator = ccas.iterator(); + while (iterator.hasNext()) { + ccasValue = ccasValue + iterator.next().toString() + " "; + } + return ccasValue; + } + + /** + * Takes in @param value representing the level of friendship value + * @return a number of hearts string. + */ + public static String changeLevelOfFriendshipToHeart(String value) { + int intValue = Integer.parseInt(value); + String heartString = ""; + for (int i = 0; i < intValue; i++) { + heartString = heartString + '\u2665' + " "; + } + return heartString; + } + +``` +###### \java\seedu\address\ui\PersonListPanel.java +``` java + @Subscribe + private void handleJumpToGoalListRequestEvent(JumpToListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollToGoal(event.targetIndex); + } + + /** + * Scrolls to the {@code GoalCard} at the {@code index} and selects it. + */ + private void scrollToGoal(int index) { + Platform.runLater(() -> { + goalListView.scrollTo(index); + goalListView.getSelectionModel().clearAndSelect(index); + }); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code GoalCard}. + */ + class GoalListViewCell extends ListCell { + + @Override + protected void updateItem(GoalCard goal, boolean empty) { + super.updateItem(goal, empty); + + if (empty || goal == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(goal.getRoot()); + } + } + } +} +``` +###### \java\seedu\address\ui\StatusBarFooter.java +``` java + private void setGoalCompletion(int goalCompletion) { + Platform.runLater(() -> this.goalCompletionStatus.setText("Goal " + goalCompletion + "% completed.")); + } + + public int getGoalCompletion(ObservableList goalList) { + int totalGoal = goalList.size(); + int totalGoalCompleted = 0; + String completionStatus; + for (int i = 0; i < totalGoal; i++) { + completionStatus = goalList.get(i).getCompletion().value; + totalGoalCompleted += isCompletedGoal(completionStatus); + } + int percentageGoalCompletion = (int) (((float) totalGoalCompleted / totalGoal) * PERCENTAGE_KEY_NUMBER); + return percentageGoalCompletion; + } + + /** + * @param completionStatus gives a String that should be either "true" or "false", indicating if the goal is + * completed. + * @return 1 or 0 + */ + private int isCompletedGoal(String completionStatus) { + int valueToAdd; + if (completionStatus.equals("true")) { + valueToAdd = 1; + } else { + valueToAdd = 0; + } + return valueToAdd; + } +``` +###### \resources\view\BubblegumTheme.css +``` css +.background { + -fx-background-color: derive(#ffdae0, 20%); + background-color: #ffb6c1; +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Lato"; + -fx-text-fill: #FFFF99; + -fx-opacity: 0.9; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Lato"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Lato Light"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Lato"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #ffdae0; + -fx-control-inner-background: #ffdae0; + -fx-background-color: #ffdae0; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Lato"; + -fx-text-fill: black; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: derive(#ffdae0, 20%); + -fx-border-color: transparent transparent transparent #4d4d4d; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: derive(#ffdae0, 20%); +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: derive(#ffdae0, 20%); +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:filled:even { + -fx-background-color: #BFEFFF; +} + +.list-cell:filled:odd { + -fx-background-color: #63D1F4; +} + +.list-cell:filled:selected { + -fx-background-color: # 00BFFF; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #3e7b91; + -fx-border-width: 1; +} + +.list-cell .label { + -fx-text-fill: black; +} + +.cell_big_label { + -fx-font-family: "Lato"; + -fx-font-size: 18px; + -fx-text-fill: #010504; +} + +.cell_small_label { + -fx-font-family: "Lato"; + -fx-font-size: 13px; + -fx-text-fill: #010504; +} + +.anchor-pane { + -fx-background-color: derive(#ffdae0, 20%); +} + +.pane-with-border { + -fx-background-color: derive(#ffdae0, 20%); + -fx-border-color: derive(#ffdae0, 10%); + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: derive(#ffdae0, 20%); + -fx-text-fill: black; +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Lato Light"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +.result-display .label { + -fx-text-fill: black !important; +} + +.status-bar .label { + -fx-font-family: "Lato"; + -fx-text-fill: black; +} + +.status-bar-with-border { + -fx-background-color: derive(#ffdae0, 30%); + -fx-border-color: derive(#ffdae0, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: black; +} + +.grid-pane { + -fx-background-color: derive(#ffdae0, 30%); + -fx-border-color: derive(#ffdae0, 30%); + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: derive(#ffdae0, 30%); +} + +.context-menu { + -fx-background-color: derive(#ffdae0, 50%); +} + +.context-menu .label { + -fx-text-fill: black; +} + +.menu-bar { + -fx-background-color: derive(#ffdae0, 20%); +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Lato Light"; + -fx-text-fill: black; + -fx-opacity: 0.9; +} +/*a*/ +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #313131; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #ffdae0; + -fx-font-family: "Lato"; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #ebebeb; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: black; + -fx-text-fill: #ffdae0; +} + +.button:focused { + -fx-border-color: black, black; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #ffdae0; + -fx-text-fill: black; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffdae0; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: #ffdae0; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #ffdae0; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: black; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#ffdae0, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: black; + -fx-text-fill: black; +} + +.scroll-bar { + -fx-background-color: derive(#BA55D3, 20%); +} + +.scroll-bar .thumb { + -fx-background-color: derive(#EE82EE, 50%); + -fx-background-insets: 3; +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-padding: 0 0 0 0; +} + +.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { + -fx-shape: " "; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 1 8 1 8; +} + +.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { + -fx-padding: 8 1 8 1; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-width: 0; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#commandTextField { + -fx-background-color: transparent #ffb6c1 transparent #ffb6c1; + -fx-background-insets: 0; + -fx-border-color: #ffb6c1 #ffb6c1 #FF7F50 #ffb6c1; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Lato Light"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, white, 10, 0, 0, 0); +} + +#resultDisplay .content { + -fx-background-color: transparent, #ffb6c1, transparent, #ffb6c1; + -fx-background-radius: 0; +} + +#tags { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#tags .label { + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} + + +#tags .teal { + -fx-text-fill: white; + -fx-background-color: #3e7b91; +} + +#tags .red { + -fx-text-fill: black; + -fx-background-color: #ff7675; +} + +#tags .yellow { + -fx-background-color: #ffeaa7; + -fx-text-fill: black; +} + +#tags .blue { + -fx-text-fill: black; + -fx-background-color: #48dbfb; +} + +#tags .orange { + -fx-text-fill: black; + -fx-background-color: #ffa502; +} + +#tags .brown { + -fx-text-fill: black; + -fx-background-color: #D7ACAC; +} + +#tags .green { + -fx-text-fill: black; + -fx-background-color: #55efc4; +} + +#tags .pink { + -fx-text-fill: black; + -fx-background-color: #fd79a8; +} + +#tags .black { + -fx-text-fill: white; + -fx-background-color: black; +} + +#tags .purple { + -fx-text-fill: black; + -fx-background-color: #a29bfe; +} + +#importance { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#importance .label { + -fx-background-color: #FFE761; + -fx-text-fill: black; + -fx-padding: 1 3 1 3; + -fx-border-radius: 3; + -fx-background-radius: 2; + -fx-font-size: 13; +} +``` +###### \resources\view\GoalListCard.fxml +``` fxml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` +###### \resources\view\LightTheme.css +``` css +.background { + -fx-background-color: derive(#ffffff, 20%); + background-color: #f5f5f5; +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Lato"; + -fx-text-fill: #cecece; + -fx-opacity: 0.9; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Lato"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Lato Light"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Lato"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #ffffff; + -fx-control-inner-background: #ffffff; + -fx-background-color: #ffffff; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Lato"; + -fx-text-fill: black; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: derive(#ffffff, 20%); + -fx-border-color: transparent transparent transparent #4d4d4d; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: derive(#ffffff, 20%); +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: derive(#ffffff, 20%); +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:filled:even { + -fx-background-color: #ebebeb; +} + +.list-cell:filled:odd { + -fx-background-color: #fcf9f9; +} + +.list-cell:filled:selected { + -fx-background-color: #dae3f2; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #3e7b91; + -fx-border-width: 1; +} + +.list-cell .label { + -fx-text-fill: black; +} + +.cell_big_label { + -fx-font-family: "Lato"; + -fx-font-size: 18px; + -fx-text-fill: #010504; +} + +.cell_small_label { + -fx-font-family: "Lato"; + -fx-font-size: 13px; + -fx-text-fill: #010504; +} + +.anchor-pane { + -fx-background-color: derive(#ffffff, 20%); +} + +.pane-with-border { + -fx-background-color: derive(#ffffff, 20%); + -fx-border-color: derive(#ffffff, 10%); + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: derive(#ffffff, 20%); + -fx-text-fill: black; +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Lato Light"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +.result-display .label { + -fx-text-fill: black !important; +} + +.status-bar .label { + -fx-font-family: "Lato"; + -fx-text-fill: black; +} + +.status-bar-with-border { + -fx-background-color: derive(#ffffff, 30%); + -fx-border-color: derive(#ffffff, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: black; +} + +.grid-pane { + -fx-background-color: derive(#ffffff, 30%); + -fx-border-color: derive(#ffffff, 30%); + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: derive(#ffffff, 30%); +} + +.context-menu { + -fx-background-color: derive(#ffffff, 50%); +} + +.context-menu .label { + -fx-text-fill: black; +} + +.menu-bar { + -fx-background-color: derive(#ffffff, 20%); +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Lato Light"; + -fx-text-fill: black; + -fx-opacity: 0.9; +} +/*a*/ +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #313131; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #ffffff; + -fx-font-family: "Lato"; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #ebebeb; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: black; + -fx-text-fill: #ffffff; +} + +.button:focused { + -fx-border-color: black, black; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #ffffff; + -fx-text-fill: black; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: black; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#ffffff, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: black; + -fx-text-fill: black; +} + +.scroll-bar { + -fx-background-color: derive(grey, 20%); +} + +.scroll-bar .thumb { + -fx-background-color: derive(#ffffff, 50%); + -fx-background-insets: 3; +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-padding: 0 0 0 0; +} + +.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { + -fx-shape: " "; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 1 8 1 8; +} + +.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { + -fx-padding: 8 1 8 1; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-width: 0; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#commandTextField { + -fx-background-color: transparent #f5f5f5 transparent #f5f5f5; + -fx-background-insets: 0; + -fx-border-color: #ffffff #ffffff #383838 #ffffff; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Lato Light"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, white, 10, 0, 0, 0); +} + +#resultDisplay .content { + -fx-background-color: transparent, #f5f5f5, transparent, #f5f5f5; + -fx-background-radius: 0; +} + +#tags { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#tags .label { + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} + + +#tags .teal { + -fx-text-fill: white; + -fx-background-color: #3e7b91; +} + +#tags .red { + -fx-text-fill: black; + -fx-background-color: #ff7675; +} + +#tags .yellow { + -fx-background-color: #ffeaa7; + -fx-text-fill: black; +} + +#tags .blue { + -fx-text-fill: black; + -fx-background-color: #48dbfb; +} + +#tags .orange { + -fx-text-fill: black; + -fx-background-color: #ffa502; +} + +#tags .brown { + -fx-text-fill: black; + -fx-background-color: #D7ACAC; +} + +#tags .green { + -fx-text-fill: black; + -fx-background-color: #55efc4; +} + +#tags .pink { + -fx-text-fill: black; + -fx-background-color: #fd79a8; +} + +#tags .black { + -fx-text-fill: white; + -fx-background-color: black; +} + +#tags .purple { + -fx-text-fill: black; + -fx-background-color: #a29bfe; +} + +#importance { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#importance .label { + -fx-background-color: #FFE761; + -fx-text-fill: black; + -fx-padding: 1 3 1 3; + -fx-border-radius: 3; + -fx-background-radius: 2; + -fx-font-size: 13; +} +``` diff --git a/collated/main/fuadsahmawi.md b/collated/main/fuadsahmawi.md new file mode 100644 index 000000000000..b802ea88ab04 --- /dev/null +++ b/collated/main/fuadsahmawi.md @@ -0,0 +1,1121 @@ +# fuadsahmawi +###### \java\seedu\address\logic\commands\AddReminderCommand.java +``` java +/** + * Adds a reminder to the Calendar. + */ +public class AddReminderCommand extends UndoableCommand { + public static final String COMMAND_WORD = "+reminder"; + public static final String COMMAND_ALIAS = "+r"; + public static final String COMMAND_ALIAS_2 = "addreminder"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a reminder to Calendar.\n" + + "Parameters: " + + PREFIX_REMINDER_TEXT + "TEXT " + + PREFIX_DATE + "START_DATETIME " + + PREFIX_END_DATE + "END_DATETIME\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_REMINDER_TEXT + " do homework " + + PREFIX_DATE + " tonight 8pm " + + PREFIX_END_DATE + " tonight 10pm"; + + public static final String MESSAGE_SUCCESS = "New reminder added: %1$s\n" + + "Disclaimer: If date & time parsed wrongly, delete the reminder and refer to User Guide for correct" + + " format of date and time"; + + public static final String MESSAGE_DUPLICATE_REMINDER = "This reminder already exists in the Calendar"; + + private final Reminder toAdd; + + /** + * Creates an AddReminderCommand to add the specified {@code Reminder} + */ + public AddReminderCommand(Reminder reminder) { + requireNonNull(reminder); + toAdd = reminder; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + model.addReminder(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicateReminderException e) { + throw new CommandException(MESSAGE_DUPLICATE_REMINDER); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddReminderCommand // instanceof handles nulls + && toAdd.equals(((AddReminderCommand) other).toAdd)); + } +} +``` +###### \java\seedu\address\logic\commands\DeleteReminderCommand.java +``` java +/** + * Deletes a reminder identified using its title in the calendar + */ +public class DeleteReminderCommand extends UndoableCommand { + public static final String COMMAND_WORD = "-reminder"; + public static final String COMMAND_ALIAS = "-r"; + public static final String COMMAND_ALIAS_2 = "deletereminder"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the reminder identified by its title & start time in the calendar.\n" + + "Parameters: REMINDER_TITLE & START_DATETIME\n" + + "Example: " + COMMAND_WORD + " text/Eat pills d/tmr 8pm"; + + public static final String MESSAGE_DELETE_REMINDER_SUCCESS = "Deleted Reminder: %1$s"; + + private Index targetIndex; + + private String dateTime; + + private ReminderTextPredicate predicate; + + private Reminder reminderToDelete; + + public DeleteReminderCommand(ReminderTextPredicate predicate, String dateTime) { + this.predicate = predicate; + this.dateTime = dateTime; + } + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(reminderToDelete); + try { + model.deleteReminder(reminderToDelete); + } catch (ReminderNotFoundException pnfe) { + throw new AssertionError("The target reminder cannot be missing"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_REMINDER_SUCCESS, reminderToDelete)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + model.updateFilteredReminderList(predicate); + List lastShownList = model.getFilteredReminderList(); + targetIndex = Index.fromOneBased(1); + if (lastShownList.size() > 1) { + for (Reminder reminder : lastShownList) { + if (reminder.getDateTime().toString().equals(dateTime)) { + reminderToDelete = reminder; + } + } + } else { + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_REMINDER_TEXT_DATE); + } + + reminderToDelete = lastShownList.get(targetIndex.getZeroBased()); + } + } +} +``` +###### \java\seedu\address\logic\commands\FindCommand.java +``` java +/** + * Finds and lists all persons in CollegeZone whose name contains any of the argument keywords. + * Keyword matching is case sensitive. + */ +public class FindCommand extends Command { + + public static final String COMMAND_WORD = "find"; + + public static final String COMMAND_ALIAS = "f"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " 1" + + PREFIX_NAME + " " + + PREFIX_PHONE + " " + + PREFIX_BIRTHDAY + " " + + PREFIX_LEVEL_OF_FRIENDSHIP + " " + + PREFIX_UNIT_NUMBER + " " + + PREFIX_CCA + " " + + PREFIX_TAG + " "; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names or tags contain any of " + + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" + + "Parameters: n/KEYWORD [MORE_KEYWORDS]... or t/KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " n/alice bob charlie"; + + public static final String MESSAGE_NOT_EDITED = "A keyword to find name or tag must be provided."; + + private TagContainsKeywordsPredicate predicateT = null; + private NameContainsKeywordsPredicate predicateN = null; + + public FindCommand(NameContainsKeywordsPredicate predicateName) { + this.predicateN = predicateName; + } + + public FindCommand(TagContainsKeywordsPredicate predicate) { + this.predicateT = predicate; + } + + @Override + public CommandResult execute() { + if (predicateT == null) { + model.updateFilteredPersonList(predicateN); + } else { + model.updateFilteredPersonList(predicateT); + } + return new CommandResult(getMessageForPersonListShownSummary(model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + if (this.predicateT == null) { + return other == this // short circuit if same object + || (other instanceof FindCommand // instanceof handles nulls + && this.predicateN.equals(((FindCommand) other).predicateN)); // state check + } else { + return other == this // short circuit if same object + || (other instanceof FindCommand // instanceof handles nulls + && this.predicateT.equals(((FindCommand) other).predicateT)); // state check + } + } + + /** + * Stores the details to edit the person with. Each non-empty field value will replace the + * corresponding field value of the person. + */ + public static class FindPersonDescriptor { + private String[] nameKeywords; + private String[] tagKeywords; + + public FindPersonDescriptor() { + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(this.nameKeywords, this.tagKeywords); + } + + public void setNameKeywords(String name) { + this.nameKeywords = name.split("\\s+"); + ; + } + + public void setTagKeywords(String tags) { + this.tagKeywords = tags.split("\\s+"); + } + + public String[] getNameKeywords() { + return this.nameKeywords; + } + + public String[] getTagKeyWords() { + return this.tagKeywords; + } + } +} +``` +###### \java\seedu\address\logic\parser\AddReminderCommandParser.java +``` java +/** + * Parses input arguments and creates a new AddReminderCommand object + */ +public class AddReminderCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddReminderCommand + * and returns an AddReminderCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddReminderCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_REMINDER_TEXT, PREFIX_DATE, PREFIX_END_DATE); + + if (!arePrefixesPresent(argMultimap, PREFIX_REMINDER_TEXT, PREFIX_DATE, PREFIX_END_DATE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddReminderCommand.MESSAGE_USAGE)); + } + + if (nattyDateAndTimeParser(argMultimap.getValue(PREFIX_DATE).get()).get().compareTo( + nattyDateAndTimeParser(argMultimap.getValue(PREFIX_END_DATE).get()).get()) > 0 + || nattyDateAndTimeParser(argMultimap.getValue(PREFIX_END_DATE).get()).get().compareTo( + LocalDateTime.now()) < 0 + || nattyDateAndTimeParser(argMultimap.getValue(PREFIX_DATE).get()).get().compareTo( + LocalDateTime.now()) < 0) { + throw new ParseException(String.format(MESSAGE_INVALID_DATE_FORMAT, AddReminderCommand.MESSAGE_USAGE)); + } + + try { + ReminderText reminderText = ParserUtil.parseReminderText(argMultimap.getValue(PREFIX_REMINDER_TEXT)).get(); + DateTime dateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATE)).get(); + EndDateTime endDateTime = ParserUtil.parseEndDateTime(argMultimap.getValue(PREFIX_END_DATE)).get(); + Reminder reminder = new Reminder(reminderText, dateTime, endDateTime); + return new AddReminderCommand(reminder); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} +``` +###### \java\seedu\address\logic\parser\DeleteReminderCommandParser.java +``` java +/** + * Parses input arguments and creates a new DeleteReminderCommand object + */ +public class DeleteReminderCommandParser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteReminderCommand + * and returns an DeleteReminderCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteReminderCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_REMINDER_TEXT, PREFIX_DATE); + + if (!arePrefixesPresent(argMultimap, PREFIX_REMINDER_TEXT, PREFIX_DATE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteReminderCommand.MESSAGE_USAGE)); + } + + String reminderText = argMultimap.getValue(PREFIX_REMINDER_TEXT).get(); + String dateTime = argMultimap.getValue(PREFIX_DATE).get(); + LocalDateTime localDateTime = nattyDateAndTimeParser(dateTime).get(); + dateTime = properReminderDateTimeFormat(localDateTime); + String trimmedArgs = reminderText.trim(); + + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteReminderCommand.MESSAGE_USAGE)); + } + + String[] nameKeywords = trimmedArgs.split("\\s+"); + + return new DeleteReminderCommand(new ReminderTextPredicate(Arrays.asList(nameKeywords)), dateTime); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} +``` +###### \java\seedu\address\logic\parser\FindCommandParser.java +``` java +/** + * Parses input arguments and creates a new FindCommand object + */ +public class FindCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindCommand + * and returns an FindCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_TAG); + + FindPersonDescriptor findPersonDescriptor = new FindPersonDescriptor(); + + argMultimap.getValue(PREFIX_NAME).ifPresent(findPersonDescriptor::setNameKeywords); + argMultimap.getValue(PREFIX_TAG).ifPresent(findPersonDescriptor::setTagKeywords); + + + if (!findPersonDescriptor.isAnyFieldEdited()) { + throw new ParseException(FindCommand.MESSAGE_NOT_EDITED); + } + + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + return new FindCommand( + new NameContainsKeywordsPredicate(Arrays.asList(findPersonDescriptor.getNameKeywords()))); + } else { + return new FindCommand( + new TagContainsKeywordsPredicate(Arrays.asList(findPersonDescriptor.getTagKeyWords()))); + } + } +} +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + + /** + * Parses a {@code String reminderText} into an {@code ReminderText}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code reminderText} is invalid. + */ + public static ReminderText parseReminderText(String reminderText) throws IllegalValueException { + requireNonNull(reminderText); + String trimmedReminderText = reminderText.trim(); + if (!ReminderText.isValidReminderText(trimmedReminderText)) { + throw new IllegalValueException(ReminderText.MESSAGE_REMINDER_TEXT_CONSTRAINTS); + } + return new ReminderText(trimmedReminderText); + } + + /** + * Parses a {@code Optional reminderText} into an {@code Optional} if {@code reminderText} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseReminderText(Optional reminderText) throws IllegalValueException { + requireNonNull(reminderText); + return reminderText.isPresent() ? Optional.of(parseReminderText(reminderText.get())) : Optional.empty(); + } + + /** + * Parses a {@code String reminderText} into an {@code ReminderText}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code reminderText} is invalid. + */ + public static DateTime parseDateTime(String dateTime) throws IllegalValueException { + requireNonNull(dateTime); + String trimmedDateTime = dateTime.trim(); + if (!DateTime.isValidDateTime(trimmedDateTime)) { + throw new IllegalValueException(DateTime.MESSAGE_DATE_TIME_CONSTRAINTS); + } + return new DateTime(trimmedDateTime); + } + + /** + * Parses a {@code Optional reminderText} into an {@code Optional} if {@code reminderText} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseDateTime(Optional dateTime) throws IllegalValueException { + requireNonNull(dateTime); + return dateTime.isPresent() ? Optional.of(parseDateTime(dateTime.get())) : Optional.empty(); + } + + /** + * Parses a {@code String reminderText} into an {@code ReminderText}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code reminderText} is invalid. + */ + public static EndDateTime parseEndDateTime(String endDateTime) throws IllegalValueException { + requireNonNull(endDateTime); + String trimmedEndDateTime = endDateTime.trim(); + if (!DateTime.isValidDateTime(trimmedEndDateTime)) { + throw new IllegalValueException(EndDateTime.MESSAGE_END_DATE_TIME_CONSTRAINTS); + } + return new EndDateTime(trimmedEndDateTime); + } + + /** + * Parses a {@code Optional reminderText} into an {@code Optional} if {@code reminderText} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseEndDateTime(Optional endDateTime) throws IllegalValueException { + requireNonNull(endDateTime); + return endDateTime.isPresent() ? Optional.of(parseEndDateTime(endDateTime.get())) : Optional.empty(); + } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Adds a reminder to CollegeZone. + * @throws DuplicateReminderException if an equivalent reminder already exists. + */ + public void addReminder (Reminder r) throws DuplicateReminderException { + reminders.add(r); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * @throws ReminderNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removeReminder(Reminder key) throws ReminderNotFoundException { + if (reminders.remove(key)) { + return true; + } else { + throw new ReminderNotFoundException(); + } + } + /** + * Replaces the given reminder {@code target} in the list with {@code editedReminder}. + * + * @throws DuplicateReminderException if updating the reminder's details causes the reminder to be equivalent to + * another existing reminder in the list. + * @throws ReminderNotFoundException if {@code target} could not be found in the list. + */ + public void updateReminder(Reminder target, Reminder editedReminder) + throws DuplicateReminderException, ReminderNotFoundException { + requireNonNull(editedReminder); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any person + // in the person list. + reminders.setReminder(target, editedReminder); + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public void addReminder(Reminder reminder) throws DuplicateReminderException { + addressBook.addReminder(reminder); + updateFilteredReminderList(PREDICATE_SHOW_ALL_REMINDERS); + indicateAddressBookChanged(); + } + + @Override + public ObservableList getFilteredReminderList() { + return FXCollections.unmodifiableObservableList(filteredReminders); + } + + @Override + public void updateFilteredReminderList(Predicate predicate) { + requireNonNull(predicate); + filteredReminders.setPredicate(predicate); + } + + @Override + public synchronized void deleteReminder(Reminder reminder) throws ReminderNotFoundException { + addressBook.removeReminder(reminder); + indicateAddressBookChanged(); + } + /* + @Override + public void updateReminder(Reminder target, Reminder editedReminder) + throws DuplicateReminderException, ReminderNotFoundException { + requireAllNonNull(target, editedReminder); + + addressBook.updateReminder(target, editedReminder); + indicateAddressBookChanged(); + } + */ +} +``` +###### \java\seedu\address\model\person\TagContainsKeywordsPredicate.java +``` java +/** + * Tests that a {@code Person}'s {@code Tags} matches any of the keywords given. + */ + +public class TagContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public TagContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + Iterator ir = person.getTags().iterator(); + StringBuilder tag = new StringBuilder(); + while (ir.hasNext()) { + tag.append(ir.next().tagName); + tag.append(" "); + } + + String tagS = tag.toString(); + + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(tagS, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TagContainsKeywordsPredicate // instanceof handles nulls + && this.keywords.equals(((TagContainsKeywordsPredicate) other).keywords)); // state check + } + +} +``` +###### \java\seedu\address\model\ReadOnlyAddressBook.java +``` java + /** + * Returns an unmodifiable view of the reminders list. + * This list will not contain any duplicate reminders. + */ + ObservableList getReminderList(); + +} +``` +###### \java\seedu\address\model\reminder\EndDateTime.java +``` java +/** + * Represents a Reminder's end date and time in the Calendar. + * Guarantees: immutable; is valid as declared in {@link #isValidEndDateTime(String)} + */ +public class EndDateTime { + + + public static final String MESSAGE_END_DATE_TIME_CONSTRAINTS = + "EndDateTime must be a valid date and time"; + public final String endDateTime; + + /** + * Constructs a {@code EndDateTime}. + * + * @param endDateTime A valid endDateTime number. + */ + public EndDateTime(String endDateTime) { + if (endDateTime.equals("")) { + this.endDateTime = ""; + } else { + checkArgument(isValidEndDateTime(endDateTime), MESSAGE_END_DATE_TIME_CONSTRAINTS); + LocalDateTime localEndDateTime = nattyDateAndTimeParser(endDateTime).get(); + this.endDateTime = properReminderDateTimeFormat(localEndDateTime); + } + } + + /** + * Returns true if a given string is a valid person endDateTime number. + */ + public static boolean isValidEndDateTime(String test) { + Optional localEndDateTime = nattyDateAndTimeParser(test); + if (localEndDateTime.isPresent()) { + return true; + } else { + return false; + } + + } + + @Override + public String toString() { + return endDateTime; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EndDateTime // instanceof handles nulls + && this.endDateTime.equals(((EndDateTime) other).endDateTime)); // state check + } + + @Override + public int hashCode() { + return endDateTime.hashCode(); + } +} +``` +###### \java\seedu\address\model\reminder\exceptions\DuplicateReminderException.java +``` java +/** + * Signals that the operation will result in duplicate Reminder objects. + */ +public class DuplicateReminderException extends DuplicateDataException { + public DuplicateReminderException() { + super("Operation will result in duplicate reminders"); + } +} +``` +###### \java\seedu\address\model\reminder\exceptions\ReminderNotFoundException.java +``` java +/** + * Signals that the operation is unable to find the specified reminder. + */ +public class ReminderNotFoundException extends Exception { +} +``` +###### \java\seedu\address\model\reminder\Reminder.java +``` java +/** + * Represents a Reminder + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Reminder { + + private final ReminderText reminderText; + private final DateTime dateTime; + private final EndDateTime endDateTime; + + /** + * Every field must be present and not null. + */ + + public Reminder(ReminderText reminderText, DateTime dateTime, EndDateTime endDateTime) { + requireAllNonNull(reminderText, dateTime); + this.reminderText = reminderText; + this.dateTime = dateTime; + this.endDateTime = endDateTime; + } + + public ReminderText getReminderText() { + return reminderText; + } + + public DateTime getDateTime() { + return dateTime; + } + + public EndDateTime getEndDateTime() { + return endDateTime; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Reminder)) { + return false; + } + + Reminder otherReminder = (Reminder) other; + return otherReminder.getReminderText().equals(this.getReminderText()) + && otherReminder.getDateTime().equals(this.getDateTime()) + && otherReminder.getEndDateTime().equals(this.getEndDateTime()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(reminderText, dateTime, endDateTime); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(" Reminder: ") + .append(getReminderText()) + .append(" Date & Time: ") + .append(getDateTime()) + .append(" End Date & Time: ") + .append(getEndDateTime()); + + return builder.toString(); + } +} +``` +###### \java\seedu\address\model\reminder\ReminderText.java +``` java +/** + * Represents a Reminder's text in the Calendar. + * Guarantees: immutable; is valid as declared in {@link #isValidReminderText(String)} + */ +public class ReminderText { + + public static final String MESSAGE_REMINDER_TEXT_CONSTRAINTS = + "Reminder text can be any expression that are not just whitespaces."; + public static final String REMINDER_TEXT_VALIDATION_REGEX = "^(?!\\s*$).+"; + public final String reminderText; + + /** + * Constructs a {@code ReminderText}. + * + * @param reminderText A valid reminder text. + */ + public ReminderText(String reminderText) { + requireNonNull(reminderText); + checkArgument(isValidGoalText(reminderText), MESSAGE_REMINDER_TEXT_CONSTRAINTS); + this.reminderText = reminderText; + } + + /** + * Returns true if a given string is a valid reminder text. + */ + public static boolean isValidReminderText(String test) { + return test.matches(REMINDER_TEXT_VALIDATION_REGEX); + } + + /** + * Returns true if a given string is a valid reminder text. + */ + public static boolean isValidGoalText(String test) { + return test.matches(REMINDER_TEXT_VALIDATION_REGEX); + } + + @Override + public String toString() { + return reminderText; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.address.model.goal.GoalText // instanceof handles nulls + && this.reminderText.equals((( + seedu.address.model.reminder.ReminderText) other).reminderText)); // state check + } + + @Override + public int hashCode() { + return reminderText.hashCode(); + } + +} +``` +###### \java\seedu\address\model\reminder\ReminderTextPredicate.java +``` java +/** + * Tests that a {@code Reminder}'s {@code ReminderText} matches any of the keywords given. + */ +public class ReminderTextPredicate implements Predicate { + private final List keywords; + + public ReminderTextPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Reminder reminder) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(reminder.getReminderText().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ReminderTextPredicate // instanceof handles nulls + && this.keywords.equals(((ReminderTextPredicate) other).keywords)); // state check + } +} +``` +###### \java\seedu\address\model\reminder\UniqueReminderList.java +``` java +/** + * A list of reminders that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Reminder#equals(Object) + */ +public class UniqueReminderList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent reminder as the given argument. + */ + public boolean contains(Reminder toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a reminder to the list. + * + * @throws DuplicateReminderException if the reminder to add is a duplicate of an existing reminder in the list. + */ + public void add(Reminder toAdd) throws DuplicateReminderException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateReminderException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the reminder {@code target} in the list with {@code editedReminder}. + * + * @throws DuplicateReminderException if the replacement is equivalent to another existing reminder in the list. + * @throws ReminderNotFoundException if {@code target} could not be found in the list. + */ + public void setReminder(Reminder target, Reminder editedReminder) + throws DuplicateReminderException, ReminderNotFoundException { + requireNonNull(editedReminder); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new ReminderNotFoundException(); + } + + if (!target.equals(editedReminder) && internalList.contains(editedReminder)) { + throw new DuplicateReminderException(); + } + + internalList.set(index, editedReminder); + } + + /** + * Removes the equivalent reminder from the list. + * + * @throws ReminderNotFoundException if no such reminder could be found in the list. + */ + public boolean remove(Reminder toRemove) throws ReminderNotFoundException { + requireNonNull(toRemove); + final boolean reminderFoundAndDeleted = internalList.remove(toRemove); + if (!reminderFoundAndDeleted) { + throw new ReminderNotFoundException(); + } + return reminderFoundAndDeleted; + } + + public void setReminders(UniqueReminderList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setReminders(List reminders) throws DuplicateReminderException { + requireAllNonNull(reminders); + final UniqueReminderList replacement = new UniqueReminderList(); + for (final Reminder reminder : reminders) { + replacement.add(reminder); + } + setReminders(replacement); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueReminderList // instanceof handles nulls + && this.internalList.equals(((UniqueReminderList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} +``` +###### \java\seedu\address\model\util\SampleReminderDataUtil.java +``` java +/** + * Contains utility methods for populating {@code CollegeZone} with sample reminder data. + */ +public class SampleReminderDataUtil { + + public static Reminder[] getSampleReminders() { + return new Reminder[] { + + new Reminder(new ReminderText("CS2103T Submission"), + new DateTime("2018-04-15 23:00"), + new EndDateTime("2018-04-15 23:59")), + new Reminder(new ReminderText("Gym Session"), + new DateTime("2018-04-13 14:00"), + new EndDateTime("2018-04-13 16:00")), + new Reminder(new ReminderText("Gym Session"), + new DateTime("2018-04-06 14:00"), + new EndDateTime("2018-04-06 16:00")), + new Reminder(new ReminderText("Gym Session"), + new DateTime("2018-04-20 14:00"), + new EndDateTime("2018-04-20 16:00")), + new Reminder(new ReminderText("Gym Session"), + new DateTime("2018-04-27 14:00"), + new EndDateTime("2018-04-27 16:00")), + new Reminder(new ReminderText("Recess Week"), + new DateTime("2018-04-23 00:00"), + new EndDateTime("2018-04-27 23:59")), + new Reminder(new ReminderText("CS2103T Software Demo"), + new DateTime("2018-04-19 09:00"), + new EndDateTime("2018-04-19 10:00")), + new Reminder(new ReminderText("CS2103T Group Meeting"), + new DateTime("2018-04-14 11:00"), + new EndDateTime("2018-04-14 18:00")), + new Reminder(new ReminderText("Chalet"), + new DateTime("2018-04-21 10:00"), + new EndDateTime("2018-04-22 20:00")), + new Reminder(new ReminderText("Medical Appointment"), + new DateTime("2018-04-05 15:00"), + new EndDateTime("2018-04-05 17:00")), + }; + } +} +``` +###### \java\seedu\address\storage\XmlAdaptedReminder.java +``` java +/** + * JAXB-friendly version of the Reminder. + */ +public class XmlAdaptedReminder { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Reminder's %s field is missing!"; + + @XmlElement(required = true) + private String reminderText; + @XmlElement(required = true) + private String dateTime; + @XmlElement(required = true) + private String endDateTime; + + /** + * Constructs an XmlAdaptedReminder. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedReminder() {} + + /** + * Constructs an {@code XmlAdaptedReminder} with the given person details. + */ + public XmlAdaptedReminder(String reminderText, String dateTime, String endDateTime) { + this.reminderText = reminderText; + this.dateTime = dateTime; + this.endDateTime = endDateTime; + } + + /** + * Converts a given Reminder into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedReminder + */ + public XmlAdaptedReminder(Reminder source) { + reminderText = source.getReminderText().toString(); + dateTime = source.getDateTime().toString(); + endDateTime = source.getEndDateTime().toString(); + } + + /** + * Converts this jaxb-friendly adapted reminder object into the model's Reminder object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted reminder + */ + public Reminder toModelType() throws IllegalValueException { + if (this.reminderText == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + ReminderText.class.getSimpleName())); + } + if (!ReminderText.isValidReminderText(this.reminderText)) { + throw new IllegalValueException(ReminderText.MESSAGE_REMINDER_TEXT_CONSTRAINTS); + } + final ReminderText reminderText = new ReminderText(this.reminderText); + + if (this.dateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + DateTime.class.getSimpleName())); + } + if (!DateTime.isValidDateTime(this.dateTime)) { + throw new IllegalValueException(DateTime.MESSAGE_DATE_TIME_CONSTRAINTS); + } + final DateTime dateTime = new DateTime(this.dateTime); + + if (this.endDateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + EndDateTime.class.getSimpleName())); + } + if (!DateTime.isValidDateTime(this.endDateTime)) { + throw new IllegalValueException(EndDateTime.MESSAGE_END_DATE_TIME_CONSTRAINTS); + } + final EndDateTime endDateTime = new EndDateTime(this.endDateTime); + + return new Reminder(reminderText, dateTime, endDateTime); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedReminder)) { + return false; + } + + XmlAdaptedReminder otherPerson = (XmlAdaptedReminder) other; + return Objects.equals(reminderText, otherPerson.reminderText) + && Objects.equals(dateTime, otherPerson.dateTime) + && Objects.equals(endDateTime, otherPerson.endDateTime); + } +} +``` +###### \java\seedu\address\ui\CalendarPanel.java +``` java +/** + * The Calendar Panel of the App. + */ +public class CalendarPanel extends UiPart { + private static final String FXML = "CalendarPanel.fxml"; + + private CalendarView calendarView; + + private ObservableList reminderList; + + private ObservableList personList; + + public CalendarPanel(ObservableList reminderList, ObservableList personList) { + super(FXML); + + this.reminderList = reminderList; + this.personList = personList; + + calendarView = new CalendarView(); + setupCalendar(); + updateCalendar(); + registerAsAnEventHandler(this); + } + + @Subscribe + private void handleNewCalendarEvent(AddressBookChangedEvent event) { + reminderList = event.data.getReminderList(); + personList = event.data.getPersonList(); + Platform.runLater(this::updateCalendar); + } + + /** + * Updates the Calendar with Reminders that are already added + */ + private void updateCalendar() { + setDateAndTime(); + CalendarSource myCalendarSource = new CalendarSource("Reminders and Meetups"); + Calendar calendarRDue = new Calendar("Reminders Already Due"); + Calendar calendarRNotDue = new Calendar("Reminders Not Due"); + Calendar calendarM = new Calendar("Meetups"); + calendarRDue.setStyle(Calendar.Style.getStyle(4)); + calendarRDue.setLookAheadDuration(Duration.ofDays(365)); + calendarRNotDue.setStyle(Calendar.Style.getStyle(1)); + calendarRNotDue.setLookAheadDuration(Duration.ofDays(365)); + calendarM.setStyle(Calendar.Style.getStyle(3)); + myCalendarSource.getCalendars().add(calendarRDue); + myCalendarSource.getCalendars().add(calendarRNotDue); + myCalendarSource.getCalendars().add(calendarM); + for (Reminder reminder : reminderList) { + LocalDateTime ldtstart = nattyDateAndTimeParser(reminder.getDateTime().toString()).get(); + LocalDateTime ldtend = nattyDateAndTimeParser(reminder.getEndDateTime().toString()).get(); + LocalDateTime now = LocalDateTime.now(); + if (now.isBefore(ldtend)) { + calendarRNotDue.addEntry(new Entry( + reminder.getReminderText().toString(), new Interval(ldtstart, ldtend))); + } else { + calendarRDue.addEntry(new Entry(reminder.getReminderText().toString(), new Interval(ldtstart, ldtend))); + } + } +``` +###### \java\seedu\address\ui\CalendarPanel.java +``` java + private void setDateAndTime() { + calendarView.setToday(LocalDate.now()); + calendarView.setTime(LocalTime.now()); + calendarView.getCalendarSources().clear(); + } + + private void setupCalendar() { + calendarView.setRequestedTime(LocalTime.now()); + calendarView.setToday(LocalDate.now()); + calendarView.setTime(LocalTime.now()); + calendarView.setShowAddCalendarButton(false); + calendarView.setShowSearchField(false); + calendarView.setShowSearchResultsTray(false); + calendarView.setShowPrintButton(false); + calendarView.showMonthPage(); + } + + public CalendarView getRoot() { + return this.calendarView; + } + +} +``` diff --git a/collated/main/sham-sheer.md b/collated/main/sham-sheer.md new file mode 100644 index 000000000000..482052ba211b --- /dev/null +++ b/collated/main/sham-sheer.md @@ -0,0 +1,599 @@ +# sham-sheer +###### \java\seedu\address\logic\CommandFormatListUtil.java +``` java +/** + * Initialises and returns a list which contains different command formats + */ +public final class CommandFormatListUtil { + private static ArrayList commandFormatList; + + public static ArrayList getCommandFormatList () { + commandFormatList = new ArrayList<>(); + createCommandFormatList(); + return commandFormatList; + } + + /** + * Creates commandFormatList for existing commands + */ + private static void createCommandFormatList() { + commandFormatList.add(AddCommand.COMMAND_FORMAT); + commandFormatList.add(AddGoalCommand.COMMAND_FORMAT); + commandFormatList.add(AddReminderCommand.COMMAND_WORD); + commandFormatList.add(ClearCommand.COMMAND_WORD); + commandFormatList.add(CompleteGoalCommand.COMMAND_WORD); + commandFormatList.add(DeleteCommand.COMMAND_WORD); + commandFormatList.add(DeleteGoalCommand.COMMAND_WORD); + commandFormatList.add(DeleteMeetCommand.COMMAND_WORD); + commandFormatList.add(DeleteReminderCommand.COMMAND_ALIAS_2); + commandFormatList.add(EditCommand.COMMAND_FORMAT); + commandFormatList.add(EditGoalCommand.COMMAND_WORD); + commandFormatList.add(ExitCommand.COMMAND_WORD); + commandFormatList.add(FindCommand.COMMAND_FORMAT); + commandFormatList.add(HelpCommand.COMMAND_WORD); + commandFormatList.add(HistoryCommand.COMMAND_WORD); + commandFormatList.add(ListCommand.COMMAND_WORD); + commandFormatList.add(MeetCommand.COMMAND_WORD); + commandFormatList.add(OngoingGoalCommand.COMMAND_WORD); + commandFormatList.add(RateCommand.COMMAND_WORD); + commandFormatList.add(RedoCommand.COMMAND_WORD); + commandFormatList.add(SelectCommand.COMMAND_WORD); + commandFormatList.add(SortCommand.COMMAND_WORD); + commandFormatList.add(SortGoalCommand.COMMAND_WORD); + commandFormatList.add(ShowLofCommand.COMMAND_WORD); + commandFormatList.add(ThemeCommand.COMMAND_WORD); + commandFormatList.add(UndoCommand.COMMAND_WORD); + + //sorting the commandFormatList + Collections.sort(commandFormatList); + } +} +``` +###### \java\seedu\address\logic\commands\DeleteMeetCommand.java +``` java +/** + * Removes the meet up set with a person using the person's displayed index from CollegeZone. + */ +public class DeleteMeetCommand extends UndoableCommand { + public static final String COMMAND_WORD = "-meet"; + + public static final String COMMAND_ALIAS = "-m"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the person's meet date identified by the index number used in the last person listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "You are not meeting %1$s anymore. "; + + private final Index targetIndex; + + private Person personToDelete; + + public DeleteMeetCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(personToDelete); + try { + model.deleteMeetDate(personToDelete); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target person cannot be missing"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + personToDelete = lastShownList.get(targetIndex.getZeroBased()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteMeetCommand // instanceof handles nulls + && this.targetIndex.equals(((DeleteMeetCommand) other).targetIndex) // state check + && Objects.equals(this.personToDelete, ((DeleteMeetCommand) other).personToDelete)); + } + + +} +``` +###### \java\seedu\address\logic\commands\MeetCommand.java +``` java +/** + * Adds a meeting to CollegeZone. + */ +public class MeetCommand extends UndoableCommand { + public static final String COMMAND_WORD = "meet"; + public static final String COMMAND_ALIAS = "m"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds the date of meetup for the person identified " + + "by the index number used in the last person listing. " + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_DATE + "[REMARK]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_DATE + "01/04/2018"; + + + public static final String MESSAGE_ADD_MEETDATE_SUCCESS = "%1$s added for meet up! Check out your Calendar!"; + public static final String MESSAGE_DELETE_MEETDATE_SUCCESS = "You are not meeting %1$s anymore!!"; + public static final String MESSAGE_DUPLICATE_PERSON = "This person has already been set to have meeting."; + + private final Index targetIndex; + private final Meet date; + + private Person personToEdit; + private Person editedPerson; + + /** + * @param targetIndex of the person in the filtered person list you want to meet + * @param date you want to meet the person + */ + + public MeetCommand(Index targetIndex, Meet date) { + requireNonNull(targetIndex); + requireNonNull(date); + + this.targetIndex = targetIndex; + this.date = date; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(personToEdit); + requireNonNull(editedPerson); + + try { + model.updatePerson(personToEdit, editedPerson); + } catch (DuplicatePersonException dpe) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } catch (PersonNotFoundException pnfe) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + return new CommandResult(generateSuccessMessage(editedPerson)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + personToEdit = lastShownList.get(targetIndex.getZeroBased()); + editedPerson = new Person(personToEdit.getName(), personToEdit.getPhone(), personToEdit.getBirthday(), + personToEdit.getLevelOfFriendship(), personToEdit.getUnitNumber(), personToEdit.getCcas(), + date, personToEdit.getTags()); + } + + /** + * Generates a command execution success message based on whether the remark is added to or removed from + * {@code personToEdit}. + */ + private String generateSuccessMessage(Person personToEdit) { + String message = !date.value.isEmpty() ? MESSAGE_ADD_MEETDATE_SUCCESS : MESSAGE_DELETE_MEETDATE_SUCCESS; + return String.format(message, personToEdit.getName()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof MeetCommand)) { + return false; + } + + // state check + MeetCommand e = (MeetCommand) other; + return targetIndex.equals(e.targetIndex) + && date.equals(e.date); + } + + + + + +} +``` +###### \java\seedu\address\logic\commands\SortCommand.java +``` java +/** + * Sort the persons in CollegeZone based on the users parameters + */ +public class SortCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "sort"; + + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid sort type: %1$s"; + + public static final String MESSAGE_EMPTY_LIST = "CollegeZone student list is empty, There is nothing to sort!"; + + public static final String MESSAGE_SORTED_SUCCESS_LEVEL_OF_FRIENDSHIP = "List sorted according to Friendship lvl!"; + + public static final String MESSAGE_SORTED_SUCCESS_MEET_DATE = "List sorted according to your latest meet date!"; + + public static final String MESSAGE_SORTED_SUCCESS_BIRTHDAY = "List sorted according to show latest birthday!"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sorts the person list identified by the index number used in the last person listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + private final Index index; + + private final ObservableList internalList = FXCollections.observableArrayList(); + + public SortCommand(Index index) { + requireNonNull(index); + this.index = index; + } + + + @Override + public CommandResult executeUndoableCommand() { + try { + model.sortPersons(index); + } catch (IndexOutOfBoundsException ioe) { + throw new AssertionError("The index is out of bounds"); + } + if (index.getOneBased() == 1) { + return new CommandResult(String.format(MESSAGE_SORTED_SUCCESS_LEVEL_OF_FRIENDSHIP)); + } + if (index.getOneBased() == 2) { + return new CommandResult(String.format(MESSAGE_SORTED_SUCCESS_MEET_DATE)); + } + return new CommandResult(String.format(MESSAGE_SORTED_SUCCESS_BIRTHDAY)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (index.getOneBased() > 3) { + throw new CommandException(String.format(SortCommand.MESSAGE_INVALID_COMMAND_FORMAT, index.getOneBased())); + } + if (lastShownList.size() == 0) { + throw new CommandException(String.format(SortCommand.MESSAGE_EMPTY_LIST)); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortCommand // instanceof handles nulls + && this.index.equals(((SortCommand) other).index)); // state check + } + + +} + +``` +###### \java\seedu\address\logic\parser\MeetCommandParser.java +``` java +/** + * Parses input arguments and creates a new {@code RemarkCommand} object + */ +public class MeetCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the {@code MeetCommand} + * and returns a {@code MeetCommand} object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public MeetCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_DATE); + + Index index; + + if (!arePrefixesPresent(argMultimap, PREFIX_DATE) || argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MeetCommand.MESSAGE_USAGE)); + } + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + Meet meetDate = ParserUtil.parseMeetDate(argMultimap.getValue(PREFIX_DATE)).get(); + return new MeetCommand(index, meetDate); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String unitNumber} into an {@code UnitNumber}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code unitNumber} is invalid. + */ + public static Meet parseMeetDate(String meetDate) throws IllegalValueException { + requireNonNull(meetDate); + String trimmedMeetDate = meetDate.trim(); + if (!Meet.isValidDate(meetDate)) { + throw new IllegalValueException(Meet.MESSAGE_DATE_CONSTRAINTS); + } + return new Meet(trimmedMeetDate); + } + /** + * Parses a {@code Optional meetDate} into an {@code Optional} if {@code meetDate} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseMeetDate(Optional meetDate) throws IllegalValueException { + requireNonNull(meetDate); + return meetDate.isPresent() ? Optional.of(parseMeetDate(meetDate.get())) : Optional.empty(); + } + +``` +###### \java\seedu\address\logic\parser\SortCommandParser.java +``` java +/** + * Parses input arguments and creates a new DeleteCommand object + */ +public class SortCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteCommand + * and returns an DeleteCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SortCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new SortCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + } + +} +``` +###### \java\seedu\address\model\AddressBook.java +``` java + public void sortPersons(Index index) throws IndexOutOfBoundsException { + this.persons.sortPersons(index); + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public void deleteMeetDate (Person person) throws PersonNotFoundException { + addressBook.removeMeetFromPerson(person); + indicateAddressBookChanged(); + } + + @Override + public synchronized void sortPersons(Index index) throws IndexOutOfBoundsException { + addressBook.sortPersons(index); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + indicateAddressBookChanged(); + } + + + //=========== Filtered Person List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * {@code addressBook} + */ +``` +###### \java\seedu\address\model\person\Birthday.java +``` java + /** + * Converts Birth date to a time that is relative to current date, for sorting purposes + */ + public static long birthDateToInt(String date) { + Calendar calendar = Calendar.getInstance(); + long longDate = convertbirthDateToSeconds(date.toString()); + long currentDate = calendar.getTimeInMillis(); + long timeDiff = longDate - currentDate; + if (timeDiff < 0) { + return Long.MAX_VALUE; + } else { + return timeDiff; + } + } + + /** + * Converts Birth date to seconds + */ + public static long convertbirthDateToSeconds(String date) { + if (date == "") { + return 0; + } + int day = Integer.parseInt(date.toString().substring(0, + 2)); + int month = Integer.parseInt(date.toString().substring(3, + 5)); + int year = 2018; + Calendar calendar = new GregorianCalendar(); + calendar.set(year, month - 1, day); + long seconds = calendar.getTimeInMillis(); + return seconds; + } + +``` +###### \java\seedu\address\model\person\Meet.java +``` java +/** + * Represents a Person's date of meeting in the address book. + * Guarantees: immutable; is always valid + */ +public class Meet { + public static final String MESSAGE_DATE_CONSTRAINTS = + "Make sure date is in this format: DD/MM/YYYY"; + public static final String DATE_VALIDATION_REGEX = + "^(((0[1-9]|[12]\\d|3[01])\\/(0[13578]|1[02])\\/((19|[2-9]\\d)\\d{2}))|((0[1-9]|[12]\\d|30)\\/" + + "(0[13456789]|1[012])\\/((19|[2-9]\\d)\\d{2}))|((0[1-9]|1\\d|2[0-8])\\/02\\/((19|[2-9]\\d)\\" + + "d{2}))|(29\\/02\\/((1[6-9]|[2-9]\\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579]" + + "[26])00))))$"; + + public final String value; + + public Meet(String meet) { + requireNonNull(meet); + if (meet.isEmpty()) { + this.value = ""; + } else { + checkArgument(isValidDate(meet), MESSAGE_DATE_CONSTRAINTS); + this.value = meet; + } + } + /** + * Converts date to seconds + */ + public static long convertDateToSeconds(String date) { + if (date == "") { + return 0; + } + int day = Integer.parseInt(date.toString().substring(0, + 2)); + int month = Integer.parseInt(date.toString().substring(3, + 5)); + int year = Integer.parseInt(date.toString().substring(6, + 10)); + Calendar calendar = new GregorianCalendar(); + calendar.set(year, month - 1, day); + long seconds = calendar.getTimeInMillis(); + return seconds; + } + + /** + * Converts meet date to a time that is relative to current date, for sorting purposes + */ + public static long dateToInt(String date) { + Calendar calendar = Calendar.getInstance(); + long longDate = convertDateToSeconds(date.toString()); + long currentDate = calendar.getTimeInMillis(); + long timeDiff = longDate - currentDate; + if (timeDiff < 0) { + return Long.MAX_VALUE; + } else { + return timeDiff; + } + } + + + public static boolean isValidDate(String test) { + return test.matches(DATE_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Meet // instanceof handles nulls + && this.value.equals(((Meet) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + + } +} +``` +###### \java\seedu\address\model\person\UniquePersonList.java +``` java + /** + * Sorting method + */ + public void sortExecution(Index index, ObservableList internalList) { + requireNonNull(index); + if (index.getOneBased() > 3) { + throw new IndexOutOfBoundsException(); + } + if (index.getOneBased() == 1) { + Comparator comparator = Comparator.comparingInt(Person::getLevelOfFriendshipInt); + FXCollections.sort(internalList, comparator); + FXCollections.reverse(internalList); + } + if (index.getOneBased() == 2) { + Comparator comparator = Comparator.comparingLong(Person::getMeetDateInt); + FXCollections.sort(internalList, comparator); + } + if (index.getOneBased() == 3) { + Comparator comparator = Comparator.comparingLong(Person::getBirthdayInt); + FXCollections.sort(internalList, comparator); + } + } +} +``` +###### \java\seedu\address\storage\XmlAdaptedPerson.java +``` java + final Meet meetDate = new Meet(this.meetDate); + +``` +###### \java\seedu\address\ui\CalendarPanel.java +``` java + for (Person person : personList) { + String meetDate = person.getMeetDate().toString(); + if (!meetDate.isEmpty()) { + int day = Integer.parseInt(meetDate.substring(0, + 2)); + int month = Integer.parseInt(meetDate.substring(3, + 5)); + int year = Integer.parseInt(meetDate.substring(6, + 10)); + calendarM.addEntry(new Entry("Meeting " + person.getName().toString(), + new Interval(LocalDate.of(year, month, day), LocalTime.of(12, 0), + LocalDate.of(year, month, day), LocalTime.of(13, 0)))); + } + } + calendarView.getCalendarSources().add(myCalendarSource); + } + +``` +###### \java\seedu\address\ui\CommandBox.java +``` java + /** + * Sets the commandbox to completed command format if the entered substring of the command is valid + * @param text is the command which is to be autocompleted + */ + private void autocompleteCommand(String text) { + ArrayList commandFormatList = CommandFormatListUtil.getCommandFormatList(); + + //retrieve the list of words which begin with text + List autocompleteCommandList = commandFormatList.stream() + .filter(s -> s.startsWith(text)) + .collect(Collectors.toList()); + + //replace input in text field with matched keyword + if (!autocompleteCommandList.isEmpty()) { + replaceText(autocompleteCommandList.get(0)); + } + + } + +} +``` diff --git a/collated/main/zuweitrack.md b/collated/main/zuweitrack.md new file mode 100644 index 000000000000..4da5310ecb93 --- /dev/null +++ b/collated/main/zuweitrack.md @@ -0,0 +1,341 @@ +# zuweitrack +###### \java\seedu\address\logic\commands\RateCommand.java +``` java +/** + * Rates existing person(s) in CollegeZone. + */ +public class RateCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "rate"; + + public static final String COMMAND_ALIAS = "rt"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Rates person(s) in College Zone " + + "and changes the level of friendship " + + "by the index number used in the latest listing.\n" + + "Existing level of friendship will be overwritten by the input values.\n" + + "Parameters: INDEX(s) (must be a positive integer) " + + "[" + PREFIX_LEVEL_OF_FRIENDSHIP + "LEVELOFFRIENDSHIP] (between 1 and 10)\n" + + "Example: " + COMMAND_WORD + " 1 3 " + + PREFIX_LEVEL_OF_FRIENDSHIP + "5 "; + + private static final String MESSAGE_EDIT_PERSON_SUCCESS = "Rated person(s) successfully"; + private static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + private static final String MESSAGE_PERSON_NOT_FOUND = "The selected person cannot be missing"; + private static final String MESSAGE_ONE_OR_MORE_INVALID_INDEX = + "One or more index inputs may not be valid" + + " and only the person(s) of valid indexes are being rated!"; + + private final List indexList; + private final String levelOfFriendship; + + /** + * @param indexList list of index(es) of the person in the filtered person list + * @param levelOfFriendship new level of friendship to add to the person + */ + public RateCommand(List indexList, String levelOfFriendship) { + requireNonNull(indexList); + requireNonNull(levelOfFriendship); + + this.indexList = indexList; + this.levelOfFriendship = levelOfFriendship; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + List latestList = model.getFilteredPersonList(); + for (Index index : indexList) { + + if (index.getZeroBased() >= latestList.size()) { + throw new CommandException(MESSAGE_ONE_OR_MORE_INVALID_INDEX); + } + + Person selectedPerson = latestList.get(index.getZeroBased()); + + try { + Person editedPerson = new Person(selectedPerson.getName(), selectedPerson.getPhone(), + selectedPerson.getBirthday(), new LevelOfFriendship(levelOfFriendship), + selectedPerson.getUnitNumber(), + selectedPerson.getCcas(), selectedPerson.getMeetDate(), selectedPerson.getTags()); + model.updatePerson(selectedPerson, editedPerson); + + } catch (PersonNotFoundException pnfe) { + throw new CommandException(MESSAGE_PERSON_NOT_FOUND); + } catch (DuplicatePersonException dpe) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + + } + + } + + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + return new CommandResult(MESSAGE_EDIT_PERSON_SUCCESS); + + } + +} +``` +###### \java\seedu\address\logic\commands\SeekRaCommand.java +``` java +/** + * Finds and lists the Resident Assistant (RA) of an individual RC Student + * in address book whose name contains any of the argument keywords. + * Keyword matching is case sensitive. + */ +public class SeekRaCommand extends Command { + + public static final String COMMAND_WORD = "seek"; + + public static final String COMMAND_ALIAS = "sk"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Seeks and lists all Resident Assistants (RA) of RC4 with the" + + " individual RC student whose name contain any of " + + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " alice bob charlie"; + + private final UnitNumberContainsKeywordsPredicate predicate; + + public SeekRaCommand(UnitNumberContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute() { + model.updateFilteredPersonList(predicate); + return new CommandResult(getMessageForRaShownSummary(model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SeekRaCommand // instanceof handles nulls + && this.predicate.equals(((SeekRaCommand) other).predicate)); // state check + } +} +``` +###### \java\seedu\address\logic\commands\ShowLofCommand.java +``` java +/** + * Finds and lists the person(s) + * in address book whose level of friendship matches the input value + * of the argument keywords. + */ +public class ShowLofCommand extends Command { + + public static final String COMMAND_WORD = "show"; + + public static final String COMMAND_ALIAS = "sh"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Shows person(s) in CollegeZone with the" + + " whose level of friendship contains any of " + + "specified level and displays them as a list with index numbers.\n" + + "Parameters: LEVELOFFRIENDSHIP [MORE_LEVELOFFRIENDSHIP]...\n" + + "Example: " + COMMAND_WORD + " 1 2 7"; + + private final LofContainsValuePredicate predicate; + + public ShowLofCommand(LofContainsValuePredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute() { + model.updateFilteredPersonList(predicate); + return new CommandResult(getMessageForPersonListShownSummary(model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ShowLofCommand // instanceof handles nulls + && this.predicate.equals(((ShowLofCommand) other).predicate)); // state check + } +} +``` +###### \java\seedu\address\logic\parser\RateCommandParser.java +``` java +/** + * Parses input arguments and creates a new RateCommand object + */ +public class RateCommandParser { + + /** + * Returns true if the level of friendship prefix "/*" is present + */ + private static boolean isPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix prefix) { + return Stream.of(prefix).allMatch(groupPrefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if the level of friendship is between 1 - 10 + */ + private static boolean containsValidRange(String levelOfFriendship) { + return levelOfFriendship.matches("0?[1-9]|[1][0]"); + } + + /** + * Parses the given {@code String} of arguments in the context of the RateCommand + * and returns an RateCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RateCommand parse (String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(args, PREFIX_LEVEL_OF_FRIENDSHIP); + + if (!isPrefixesPresent(argumentMultimap, PREFIX_LEVEL_OF_FRIENDSHIP)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RateCommand.MESSAGE_USAGE)); + } + String levelOfFriendship = argumentMultimap.getValue(PREFIX_LEVEL_OF_FRIENDSHIP).get(); + + if (!containsValidRange(levelOfFriendship)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RateCommand.MESSAGE_USAGE)); + } + + String preamble; + String[] indexString; + List indexList = new ArrayList<>(); + + try { + preamble = argumentMultimap.getPreamble(); + indexString = preamble.split("\\s+"); + for (String index : indexString) { + indexList.add(ParserUtil.parseIndex(index)); + } + + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RateCommand.MESSAGE_USAGE)); + } + + return new RateCommand(indexList, new String(levelOfFriendship)); + } + +} +``` +###### \java\seedu\address\logic\parser\SeekRaCommandParser.java +``` java +/** + * Parses input arguments and creates a new SeekRaCommand object + */ +public class SeekRaCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the SeekRaCommand + * and returns an SeekRaCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SeekRaCommand parse(String args) throws ParseException { + + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SeekRaCommand.MESSAGE_USAGE)); + } + + trimmedArgs = trimmedArgs + " " + "RA"; + + String[] nameKeywords = (trimmedArgs.split("\\s+")); + + return new SeekRaCommand(new UnitNumberContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + } + +} +``` +###### \java\seedu\address\logic\parser\ShowLofCommandParser.java +``` java +/** + * Parses input arguments and creates a new ShowLofCommand object + */ +public class ShowLofCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ShowLofCommand + * and returns an ShowLofCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ShowLofCommand parse(String args) throws ParseException { + + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ShowLofCommand.MESSAGE_USAGE)); + } + + String[] nameKeywords = (trimmedArgs.split("\\s+")); + + return new ShowLofCommand(new LofContainsValuePredicate(Arrays.asList(nameKeywords))); + } + +} +``` +###### \java\seedu\address\model\person\LofContainsValuePredicate.java +``` java +/** + * Tests that a {@code Person}'s {@code UnitNumber} matches any of the keywords given. + */ +public class LofContainsValuePredicate implements Predicate { + private final List keywords; + + public LofContainsValuePredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase + (person.getLevelOfFriendship().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LofContainsValuePredicate // instanceof handles nulls + && this.keywords.equals(((LofContainsValuePredicate) other).keywords)); // state check + } + +} +``` +###### \java\seedu\address\model\person\UnitNumberContainsKeywordsPredicate.java +``` java +/** + * Tests that a {@code Person}'s {@code UnitNumber} matches any of the keywords given. + */ +public class UnitNumberContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public UnitNumberContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + + Iterator ir = person.getTags().iterator(); + StringBuilder tag = new StringBuilder(); + while (ir.hasNext()) { + tag.append(ir.next().tagName); + tag.append(" "); + } + + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)) + | keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(tag.toString(), keyword)); + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UnitNumberContainsKeywordsPredicate // instanceof handles nulls + && this.keywords.equals(((UnitNumberContainsKeywordsPredicate) other).keywords)); // state check + } + +} +``` diff --git a/collated/test/deborahlow97.md b/collated/test/deborahlow97.md new file mode 100644 index 000000000000..c2b585617614 --- /dev/null +++ b/collated/test/deborahlow97.md @@ -0,0 +1,2464 @@ +# deborahlow97 +###### \java\guitests\guihandles\StatusBarFooterHandle.java +``` java + /** + * Returns the text of the 'save location' portion of the status bar. + */ + public String getGoalCompletedStatus() { + return goalCompletedStatusNode.getText(); + } + +``` +###### \java\guitests\guihandles\StatusBarFooterHandle.java +``` java + /** + * Remembers the content of the 'goal completion status' portion of the status bar. + */ + public void rememberGoalCompletedStatus() { + lastRememberedGoalCompletedStatus = getGoalCompletedStatus(); + } + + /** + * Returns true if the current content of the 'goal completion status' is different from the value remembered + * by the most + * recent {@code rememberGoalCompletedStatus()} call. + */ + public boolean isGoalCompletedStatusChanged() { + return !lastRememberedGoalCompletedStatus.equals(getGoalCompletedStatus()); + } +} +``` +###### \java\seedu\address\logic\commands\AddCommandTest.java +``` java + @Override + public void addGoal(Goal goal) throws DuplicateGoalException { + fail("This method should not be called."); + } + + @Override + public ObservableList getFilteredGoalList() { + fail("This method should not be called."); + return null; + } + + @Override + public void updateFilteredGoalList(Predicate predicate) { + fail("This method should not be called."); + } + + @Override + public void deleteGoal(Goal target) throws GoalNotFoundException { + fail("This method should not be called."); + } + + @Override + public void updateGoal(Goal target, Goal editedGoal) + throws DuplicateGoalException { + fail("This method should not be called."); + } + + @Override + public void updateGoalWithoutParameters(Goal target, Goal editedGoal) + throws GoalNotFoundException { + fail("This method should not be called."); + } + + @Override + public void sortGoal(String goalField, String goalOrder) throws EmptyGoalListException { + fail("This method should not be called."); + } + +``` +###### \java\seedu\address\logic\commands\AddGoalCommandIntegrationTest.java +``` java +/** + * Contains integration tests (interaction with the Model) for {@code AddGoalCommand}. + */ +public class AddGoalCommandIntegrationTest { + + private Model model; + + @Before + public void setUp() { + model = new ModelManager(getTypicalGoalAddressBook(), new UserPrefs()); + } + + @Test + public void execute_newGoal_success() throws Exception { + Goal validGoal = new GoalBuilder().withImportance(VALID_GOAL_IMPORTANCE_B).build(); + + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.addGoal(validGoal); + + assertCommandSuccess(prepareCommand(validGoal, model), model, + String.format(AddGoalCommand.MESSAGE_SUCCESS, validGoal), expectedModel); + } + + @Test + public void execute_duplicateGoal_throwsCommandException() { + Goal goalInList = model.getAddressBook().getGoalList().get(0); + assertCommandFailure(prepareCommand(goalInList, model), model, AddGoalCommand.MESSAGE_DUPLICATE_GOAL); + } + + /** + * Generates a new {@code AddGoalCommand} which upon execution, adds {@code goal} into the {@code model}. + */ + private AddGoalCommand prepareCommand(Goal goal, Model model) { + AddGoalCommand command = new AddGoalCommand(goal); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } +} + +``` +###### \java\seedu\address\logic\commands\AddGoalCommandTest.java +``` java +public class AddGoalCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullGoal_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new AddGoalCommand(null); + } + + @Test + public void execute_duplicateGoal_throwsCommandException() throws Exception { + AddCommandTest.ModelStub modelStub = new ModelStubThrowingDuplicateGoalException(); + Goal validGoal = new GoalBuilder().build(); + + thrown.expect(CommandException.class); + thrown.expectMessage(AddGoalCommand.MESSAGE_DUPLICATE_GOAL); + + getAddGoalCommandForGoal(validGoal, modelStub).execute(); + } + + + @Test + public void equals() { + Goal a = new GoalBuilder().withGoalText("A").build(); + Goal b = new GoalBuilder().withGoalText("B").build(); + AddGoalCommand addGoalACommand = new AddGoalCommand(a); + AddGoalCommand addGoalBCommand = new AddGoalCommand(b); + + // same object -> returns true + assertTrue(addGoalACommand.equals(addGoalACommand)); + + // same values -> returns true + AddGoalCommand addGoalACommandCopy = new AddGoalCommand(a); + assertTrue(addGoalACommand.equals(addGoalACommandCopy)); + + // different types -> returns false + assertFalse(addGoalACommand.equals(1)); + + // null -> returns false + assertFalse(addGoalACommand.equals(null)); + + // different goal -> returns false + assertFalse(addGoalACommand.equals(addGoalBCommand)); + } + + /** + * Generates a new AddGoalCommand with the details of the given goal. + */ + private AddGoalCommand getAddGoalCommandForGoal(Goal goal, Model model) { + AddGoalCommand command = new AddGoalCommand(goal); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + /** + * A Model stub that always throw a DuplicateGoalException when trying to add a goal. + */ + private class ModelStubThrowingDuplicateGoalException extends AddCommandTest.ModelStub { + @Override + public void addGoal(Goal goal) throws DuplicateGoalException { + throw new DuplicateGoalException(); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } + + /** + * A Model stub that always accept the goal being added. + */ + private class ModelStubAcceptingGoalAdded extends AddCommandTest.ModelStub { + final ArrayList goalsAdded = new ArrayList<>(); + + @Override + public void addGoal(Goal goal) throws DuplicateGoalException { + requireNonNull(goal); + goalsAdded.add(goal); + } + } +} +``` +###### \java\seedu\address\logic\commands\CompleteGoalCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for + * CompleteGoalCommand. + */ +public class CompleteGoalCommandTest { + + private Model model = new ModelManager(getTypicalGoalAddressBook(), new UserPrefs()); + + @Test + public void execute_allPreSpecifiedFieldsUnfilteredList_success() throws Exception { + Index indexLastGoal = Index.fromOneBased(model.getFilteredGoalList().size()); + Goal lastGoal = model.getFilteredGoalList().get(indexLastGoal.getZeroBased()); + + GoalBuilder goalInList = new GoalBuilder(lastGoal); + Goal completedGoal = goalInList.withCompletion(VALID_GOAL_COMPLETION_D) + .withEndDateTime(VALID_GOAL_END_DATE_TIME_STRING_D).build(); + + CompleteGoalDescriptor descriptor = new CompleteGoalDescriptorBuilder().withCompletion(VALID_GOAL_COMPLETION_D) + .withEndDateTime(VALID_GOAL_END_DATE_TIME_STRING_D).build(); + CompleteGoalCommand completeGoalCommand = prepareCommand(indexLastGoal, descriptor); + + String expectedMessage = String.format(CompleteGoalCommand.MESSAGE_COMPLETE_GOAL_SUCCESS, completedGoal); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + expectedModel.updateGoalWithoutParameters(lastGoal, completedGoal); + + assertCommandSuccess(completeGoalCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidGoalIndexUnfilteredList_failure() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredGoalList().size() + 1); + CompleteGoalDescriptor descriptor = new CompleteGoalDescriptorBuilder().withCompletion(VALID_GOAL_COMPLETION_D) + .withEndDateTime(VALID_GOAL_END_DATE_TIME_STRING_D).build(); + CompleteGoalCommand completeGoalCommand = prepareCommand(outOfBoundIndex, descriptor); + + assertCommandFailure(completeGoalCommand, model, Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + } + + @Test + public void executeUndoRedo_validIndexUnfilteredList_success() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Goal completedGoal = new GoalBuilder().build(); + Goal goalToEdit = model.getFilteredGoalList().get(INDEX_FIRST_GOAL.getZeroBased()); + CompleteGoalDescriptor descriptor = new CompleteGoalDescriptorBuilder(completedGoal).build(); + CompleteGoalCommand completeGoalCommand = prepareCommand(INDEX_FIRST_GOAL, descriptor); + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + + // edit -> first goal completed + completeGoalCommand.execute(); + undoRedoStack.push(completeGoalCommand); + + // undo -> reverts addressbook back to previous state and filtered goal list to show all goals + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + // redo -> same first goal completed again + expectedModel.updateGoalWithoutParameters(goalToEdit, completedGoal); + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void executeUndoRedo_invalidIndexUnfilteredList_failure() { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredGoalList().size() + 1); + CompleteGoalDescriptor descriptor = new CompleteGoalDescriptorBuilder().withCompletion(VALID_GOAL_COMPLETION_D) + .withEndDateTime(VALID_GOAL_END_DATE_TIME_STRING_D).build(); + CompleteGoalCommand completeGoalCommand = prepareCommand(outOfBoundIndex, descriptor); + + // execution failed -> completeGoalCommand not pushed into undoRedoStack + assertCommandFailure(completeGoalCommand, model, Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + + // no commands in undoRedoStack -> undoCommand and redoCommand fail + assertCommandFailure(undoCommand, model, UndoCommand.MESSAGE_FAILURE); + assertCommandFailure(redoCommand, model, RedoCommand.MESSAGE_FAILURE); + } + + @Test + public void equals() throws Exception { + final CompleteGoalCommand standardCommand = prepareCommand(INDEX_FIRST_GOAL, DESC_GOAL_COMPLETED_C); + + // same values -> returns true + CompleteGoalDescriptor copyDescriptor = new CompleteGoalDescriptor(DESC_GOAL_COMPLETED_C); + CompleteGoalCommand commandWithSameValues = prepareCommand(INDEX_FIRST_GOAL, copyDescriptor); + assertTrue(standardCommand.equals(commandWithSameValues)); + + // same object -> returns true + assertTrue(standardCommand.equals(standardCommand)); + + // one command preprocessed when previously equal -> returns false + commandWithSameValues.preprocessUndoableCommand(); + assertFalse(standardCommand.equals(commandWithSameValues)); + + // null -> returns false + assertFalse(standardCommand.equals(null)); + + // different types -> returns false + assertFalse(standardCommand.equals(new ClearCommand())); + + // different index -> returns false + assertFalse(standardCommand.equals(new CompleteGoalCommand(INDEX_SECOND_GOAL, DESC_GOAL_COMPLETED_C))); + + // different descriptor -> returns false + assertFalse(standardCommand.equals(new CompleteGoalCommand(INDEX_FIRST_GOAL, DESC_GOAL_COMPLETED_D))); + } + + /** + * Returns an {@code CompleteGoalCommand} with parameters {@code index} and {@code descriptor} + */ + private CompleteGoalCommand prepareCommand(Index index, CompleteGoalDescriptor descriptor) { + CompleteGoalCommand completeGoalCommand = new CompleteGoalCommand(index, descriptor); + completeGoalCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return completeGoalCommand; + } +} + +``` +###### \java\seedu\address\logic\commands\DeleteGoalCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for + * {@code DeleteGoalCommand}. + */ +public class DeleteGoalCommandTest { + + private Model model = new ModelManager(getTypicalGoalAddressBook(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() throws Exception { + Goal goalToDelete = model.getFilteredGoalList().get(INDEX_FIRST_GOAL.getZeroBased()); + DeleteGoalCommand deleteGoalCommand = prepareCommand(INDEX_FIRST_GOAL); + + String expectedMessage = String.format(DeleteGoalCommand.MESSAGE_DELETE_GOAL_SUCCESS, goalToDelete); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteGoal(goalToDelete); + + assertCommandSuccess(deleteGoalCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() throws Exception { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredGoalList().size() + 1); + DeleteGoalCommand deleteGoalCommand = prepareCommand(outOfBoundIndex); + + assertCommandFailure(deleteGoalCommand, model, Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + } + + @Test + public void executeUndoRedo_validIndexUnfilteredList_success() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Goal goalToDelete = model.getFilteredGoalList().get(INDEX_FIRST_GOAL.getZeroBased()); + DeleteGoalCommand deleteGoalCommand = prepareCommand(INDEX_FIRST_GOAL); + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + // delete -> first goal deleted + deleteGoalCommand.execute(); + undoRedoStack.push(deleteGoalCommand); + + // undo -> reverts addressbook back to previous state and filtered goal list to show all goals + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + // redo -> same first goal deleted again + expectedModel.deleteGoal(goalToDelete); + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void executeUndoRedo_invalidIndexUnfilteredList_failure() { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredGoalList().size() + 1); + DeleteGoalCommand deleteGoalCommand = prepareCommand(outOfBoundIndex); + + // execution failed -> deleteGoalCommand not pushed into undoRedoStack + assertCommandFailure(deleteGoalCommand, model, Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + + // no commands in undoRedoStack -> undoCommand and redoCommand fail + assertCommandFailure(undoCommand, model, UndoCommand.MESSAGE_FAILURE); + assertCommandFailure(redoCommand, model, RedoCommand.MESSAGE_FAILURE); + } + + @Test + public void equals() throws Exception { + DeleteGoalCommand deleteFirstCommand = prepareCommand(INDEX_FIRST_GOAL); + DeleteGoalCommand deleteSecondCommand = prepareCommand(INDEX_SECOND_GOAL); + + // same object -> returns true + assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); + + // same values -> returns true + DeleteGoalCommand deleteFirstCommandCopy = prepareCommand(INDEX_FIRST_GOAL); + assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // one command preprocessed when previously equal -> returns false + deleteFirstCommandCopy.preprocessUndoableCommand(); + assertFalse(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFirstCommand.equals(null)); + + // different goal -> returns false + assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); + } + + /** + * Returns a {@code DeleteGoalCommand} with the parameter {@code index}. + */ + private DeleteGoalCommand prepareCommand(Index index) { + DeleteGoalCommand deleteGoalCommand = new DeleteGoalCommand(index); + deleteGoalCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return deleteGoalCommand; + } + + /** + * Updates {@code model}'s filtered list to show no one. + */ + private void showNoGoal(Model model) { + model.updateFilteredGoalList(p -> false); + + assertTrue(model.getFilteredGoalList().isEmpty()); + } +} +``` +###### \java\seedu\address\logic\commands\EditGoalCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for + * EditGoalCommand. + */ +public class EditGoalCommandTest { + + private Model model = new ModelManager(getTypicalGoalAddressBook(), new UserPrefs()); + + @Test + public void execute_allFieldsSpecifiedUnfilteredList_success() throws Exception { + Goal editedGoal = new GoalBuilder().build(); + EditGoalDescriptor descriptor = new EditGoalDescriptorBuilder(editedGoal).build(); + EditGoalCommand editGoalCommand = prepareCommand(INDEX_FIRST_GOAL, descriptor); + + String expectedMessage = String.format(EditGoalCommand.MESSAGE_EDIT_GOAL_SUCCESS, editedGoal); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + expectedModel.updateGoal(model.getFilteredGoalList().get(0), editedGoal); + + assertCommandSuccess(editGoalCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_someFieldsSpecifiedUnfilteredList_success() throws Exception { + Index indexLastGoal = Index.fromOneBased(model.getFilteredGoalList().size()); + Goal lastGoal = model.getFilteredGoalList().get(indexLastGoal.getZeroBased()); + + GoalBuilder goalInList = new GoalBuilder(lastGoal); + Goal editedGoal = goalInList.withGoalText(VALID_GOAL_TEXT_B).withImportance(VALID_GOAL_IMPORTANCE_B).build(); + + EditGoalDescriptor descriptor = new EditGoalDescriptorBuilder().withGoalText(VALID_GOAL_TEXT_B) + .withImportance(VALID_GOAL_IMPORTANCE_B).build(); + EditGoalCommand editGoalCommand = prepareCommand(indexLastGoal, descriptor); + + String expectedMessage = String.format(EditGoalCommand.MESSAGE_EDIT_GOAL_SUCCESS, editedGoal); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + expectedModel.updateGoal(lastGoal, editedGoal); + + assertCommandSuccess(editGoalCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_noFieldSpecifiedUnfilteredList_success() { + EditGoalCommand editGoalCommand = prepareCommand(INDEX_FIRST_GOAL, new EditGoalDescriptor()); + Goal editedGoal = model.getFilteredGoalList().get(INDEX_FIRST_GOAL.getZeroBased()); + + String expectedMessage = String.format(EditGoalCommand.MESSAGE_EDIT_GOAL_SUCCESS, editedGoal); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + + assertCommandSuccess(editGoalCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_duplicateGoalUnfilteredList_failure() { + Goal firstGoal = model.getFilteredGoalList().get(INDEX_FIRST_GOAL.getZeroBased()); + EditGoalDescriptor descriptor = new EditGoalDescriptorBuilder(firstGoal).build(); + EditGoalCommand editGoalCommand = prepareCommand(INDEX_SECOND_GOAL, descriptor); + + assertCommandFailure(editGoalCommand, model, EditGoalCommand.MESSAGE_DUPLICATE_GOAL); + } + + @Test + public void execute_invalidGoalIndexUnfilteredList_failure() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredGoalList().size() + 1); + EditGoalDescriptor descriptor = new EditGoalDescriptorBuilder().withImportance(VALID_GOAL_IMPORTANCE_B).build(); + EditGoalCommand editGoalCommand = prepareCommand(outOfBoundIndex, descriptor); + + assertCommandFailure(editGoalCommand, model, Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + } + + @Test + public void executeUndoRedo_validIndexUnfilteredList_success() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Goal editedGoal = new GoalBuilder().build(); + Goal goalToEdit = model.getFilteredGoalList().get(INDEX_FIRST_GOAL.getZeroBased()); + EditGoalDescriptor descriptor = new EditGoalDescriptorBuilder(editedGoal).build(); + EditGoalCommand editGoalCommand = prepareCommand(INDEX_FIRST_GOAL, descriptor); + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + + // edit -> first goal edited + editGoalCommand.execute(); + undoRedoStack.push(editGoalCommand); + + // undo -> reverts addressbook back to previous state and filtered goal list to show all goals + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + // redo -> same first goal edited again + expectedModel.updateGoal(goalToEdit, editedGoal); + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void executeUndoRedo_invalidIndexUnfilteredList_failure() { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredGoalList().size() + 1); + EditGoalDescriptor descriptor = new EditGoalDescriptorBuilder().withGoalText(VALID_GOAL_TEXT_B).build(); + EditGoalCommand editGoalCommand = prepareCommand(outOfBoundIndex, descriptor); + + // execution failed -> editGoalCommand not pushed into undoRedoStack + assertCommandFailure(editGoalCommand, model, Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + + // no commands in undoRedoStack -> undoCommand and redoCommand fail + assertCommandFailure(undoCommand, model, UndoCommand.MESSAGE_FAILURE); + assertCommandFailure(redoCommand, model, RedoCommand.MESSAGE_FAILURE); + } + + @Test + public void equals() throws Exception { + final EditGoalCommand standardCommand = prepareCommand(INDEX_FIRST_GOAL, DESC_GOAL_A); + + // same values -> returns true + EditGoalDescriptor copyDescriptor = new EditGoalDescriptor(DESC_GOAL_A); + EditGoalCommand commandWithSameValues = prepareCommand(INDEX_FIRST_GOAL, copyDescriptor); + assertTrue(standardCommand.equals(commandWithSameValues)); + + // same object -> returns true + assertTrue(standardCommand.equals(standardCommand)); + + // one command preprocessed when previously equal -> returns false + commandWithSameValues.preprocessUndoableCommand(); + assertFalse(standardCommand.equals(commandWithSameValues)); + + // null -> returns false + assertFalse(standardCommand.equals(null)); + + // different types -> returns false + assertFalse(standardCommand.equals(new ClearCommand())); + + // different index -> returns false + assertFalse(standardCommand.equals(new EditGoalCommand(INDEX_SECOND_GOAL, DESC_GOAL_A))); + + // different descriptor -> returns false + assertFalse(standardCommand.equals(new EditGoalCommand(INDEX_FIRST_GOAL, DESC_GOAL_B))); + } + + /** + * Returns an {@code EditGoalCommand} with parameters {@code index} and {@code descriptor} + */ + private EditGoalCommand prepareCommand(Index index, EditGoalDescriptor descriptor) { + EditGoalCommand editGoalCommand = new EditGoalCommand(index, descriptor); + editGoalCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return editGoalCommand; + } +} +``` +###### \java\seedu\address\logic\commands\GoalCommandTestUtil.java +``` java +/** + * Contains helper methods for testing commands. + */ +public class GoalCommandTestUtil { + + public static final String VALID_GOAL_TEXT_A = "Make 10 new friends in university"; + public static final String VALID_GOAL_TEXT_B = "Drink 8 glasses of water everyday - stay hydrated!!"; + public static final String VALID_GOAL_IMPORTANCE_A = "1"; + public static final String VALID_GOAL_IMPORTANCE_B = "10"; + public static final String VALID_GOAL_START_DATE_TIME_STRING_A = "2018-03-03 10:30"; + public static final String VALID_GOAL_START_DATE_TIME_STRING_B = "2018-03-03 10:31"; + public static final String VALID_GOAL_END_DATE_TIME_STRING_A = "2018-04-04 10:30"; + public static final String VALID_GOAL_END_DATE_TIME_STRING_B = ""; + public static final String VALID_GOAL_END_DATE_TIME_STRING_C = "14/10/2018 3pm"; + public static final String VALID_GOAL_END_DATE_TIME_STRING_D = "10/10/2018 4pm"; + public static final boolean VALID_GOAL_COMPLETION_A = true; + public static final boolean VALID_GOAL_COMPLETION_B = false; + public static final boolean VALID_GOAL_COMPLETION_C = true; + public static final boolean VALID_GOAL_COMPLETION_D = true; + public static final boolean VALID_GOAL_COMPLETION_E = false; + public static final String VALID_GOAL_END_DATE_TIME = ""; + public static final String VALID_GOAL_SORT_FIELD_A = "importance"; + public static final String VALID_GOAL_SORT_FIELD_B = "startdatetime"; + public static final String VALID_GOAL_SORT_ORDER_A = "ascending"; + public static final String VALID_GOAL_SORT_ORDER_B = "descending"; + public static final String INVALID_GOAL_SORT_ORDER = " " + PREFIX_SORT_ORDER + "increasing"; + public static final String INVALID_GOAL_SORT_FIELD = " " + PREFIX_SORT_FIELD + "invalid"; + public static final String GOAL_TEXT_DESC_A = " " + PREFIX_GOAL_TEXT + VALID_GOAL_TEXT_A; + public static final String GOAL_TEXT_DESC_B = " " + PREFIX_GOAL_TEXT + VALID_GOAL_TEXT_B; + public static final String GOAL_IMPORTANCE_DESC_A = " " + PREFIX_IMPORTANCE + VALID_GOAL_IMPORTANCE_A; + public static final String GOAL_IMPORTANCE_DESC_B = " " + PREFIX_IMPORTANCE + VALID_GOAL_IMPORTANCE_B; + public static final String GOAL_SORT_ORDER_DESC_A = " " + PREFIX_SORT_ORDER + VALID_GOAL_SORT_ORDER_A; + public static final String GOAL_SORT_ORDER_DESC_B = " " + PREFIX_SORT_ORDER + VALID_GOAL_SORT_ORDER_B; + public static final String GOAL_SORT_FIELD_DESC_A = " " + PREFIX_SORT_FIELD + VALID_GOAL_SORT_FIELD_A; + public static final String GOAL_SORT_FIELD_DESC_B = " " + PREFIX_SORT_FIELD + VALID_GOAL_SORT_FIELD_B; + public static final String INVALID_IMPORTANCE_DESC = " " + PREFIX_IMPORTANCE + "-1"; + // negative numbers not allowed in importance + + public static final String INVALID_GOAL_TEXT_DESC = " " + PREFIX_GOAL_TEXT + ""; + public static final String PREAMBLE_WHITESPACE = "\t \r \n"; + public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble"; + + public static final LocalDateTime VALID_GOAL_START_DATE_TIME_A; + public static final LocalDateTime VALID_GOAL_START_DATE_TIME_B; + public static final EditGoalCommand.EditGoalDescriptor DESC_GOAL_A; + public static final EditGoalCommand.EditGoalDescriptor DESC_GOAL_B; + + public static final CompleteGoalCommand.CompleteGoalDescriptor DESC_GOAL_COMPLETED_C; + public static final CompleteGoalCommand.CompleteGoalDescriptor DESC_GOAL_COMPLETED_D; + public static final OngoingGoalCommand.OngoingGoalDescriptor DESC_GOAL_COMPLETED_E; + public static final OngoingGoalCommand.OngoingGoalDescriptor DESC_GOAL_COMPLETED_F; + static { + VALID_GOAL_START_DATE_TIME_A = getLocalDateTimeFromString(VALID_GOAL_START_DATE_TIME_STRING_A); + VALID_GOAL_START_DATE_TIME_B = getLocalDateTimeFromString(VALID_GOAL_START_DATE_TIME_STRING_B); + + DESC_GOAL_A = new EditGoalDescriptorBuilder().withImportance(VALID_GOAL_IMPORTANCE_A) + .withGoalText(VALID_GOAL_TEXT_A).build(); + DESC_GOAL_B = new EditGoalDescriptorBuilder().withGoalText(VALID_GOAL_TEXT_A) + .withImportance(VALID_GOAL_IMPORTANCE_B).build(); + DESC_GOAL_COMPLETED_C = new CompleteGoalDescriptorBuilder().withCompletion(VALID_GOAL_COMPLETION_C) + .withEndDateTime(VALID_GOAL_END_DATE_TIME_STRING_C).build(); + DESC_GOAL_COMPLETED_D = new CompleteGoalDescriptorBuilder().withCompletion(VALID_GOAL_COMPLETION_D) + .withEndDateTime(VALID_GOAL_END_DATE_TIME_STRING_D).build(); + DESC_GOAL_COMPLETED_E = new OngoingGoalDescriptorBuilder().withCompletion(VALID_GOAL_COMPLETION_E) + .withEndDateTime(VALID_GOAL_END_DATE_TIME).build(); + DESC_GOAL_COMPLETED_F = new OngoingGoalDescriptorBuilder().withCompletion(VALID_GOAL_COMPLETION_E) + .withEndDateTime(VALID_GOAL_END_DATE_TIME).build(); + } + + /** + * Executes the given {@code command}, confirms that
+ * - the result message matches {@code expectedMessage}
+ * - the {@code actualModel} matches {@code expectedModel} + */ + public static void assertCommandSuccess(Command command, Model actualModel, String expectedMessage, + Model expectedModel) { + try { + CommandResult result = command.execute(); + assertEquals(expectedMessage, result.feedbackToUser); + assertEquals(expectedModel, actualModel); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + + /** + * Executes the given {@code command}, confirms that
+ * - a {@code CommandException} is thrown
+ * - the CommandException message matches {@code expectedMessage}
+ * - the address book and the filtered goal list in the {@code actualModel} remain unchanged + */ + public static void assertCommandFailure(Command command, Model actualModel, String expectedMessage) { + // we are unable to defensively copy the model for comparison later, so we can + // only do so by copying its components. + AddressBook expectedAddressBook = new AddressBook(actualModel.getAddressBook()); + List expectedFilteredList = new ArrayList<>(actualModel.getFilteredGoalList()); + + try { + command.execute(); + fail("The expected CommandException was not thrown."); + } catch (CommandException e) { + assertEquals(expectedMessage, e.getMessage()); + assertEquals(expectedAddressBook, actualModel.getAddressBook()); + assertEquals(expectedFilteredList, actualModel.getFilteredGoalList()); + } + } + + /** + * Deletes the first GOAL in {@code model}'s list from {@code model}'s address book. + */ + public static void deleteFirstGoal(Model model) { + Goal firstGoal = model.getFilteredGoalList().get(0); + try { + model.deleteGoal(firstGoal); + } catch (GoalNotFoundException pnfe) { + throw new AssertionError("Goal in filtered list must exist in model.", pnfe); + } + } + + /** + * Returns an {@code UndoCommand} with the given {@code model} and {@code undoRedoStack} set. + */ + public static UndoCommand prepareUndoCommand(Model model, UndoRedoStack undoRedoStack) { + UndoCommand undoCommand = new UndoCommand(); + undoCommand.setData(model, new CommandHistory(), undoRedoStack); + return undoCommand; + } + + /** + * Returns a {@code RedoCommand} with the given {@code model} and {@code undoRedoStack} set. + */ + public static RedoCommand prepareRedoCommand(Model model, UndoRedoStack undoRedoStack) { + RedoCommand redoCommand = new RedoCommand(); + redoCommand.setData(model, new CommandHistory(), undoRedoStack); + return redoCommand; + } +} +``` +###### \java\seedu\address\logic\commands\OngoingGoalCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for + * OngoingGoalCommand. + */ +public class OngoingGoalCommandTest { + + private Model model = new ModelManager(getTypicalGoalAddressBook(), new UserPrefs()); + + @Test + public void execute_goalAlreadyOngoingUnfilteredList_throwsCommandException() throws Exception { + Index indexLastGoal = Index.fromOneBased(model.getFilteredGoalList().size()); + Goal lastGoal = model.getFilteredGoalList().get(indexLastGoal.getZeroBased()); + + GoalBuilder goalInList = new GoalBuilder(lastGoal); + Goal ongoingGoal = goalInList.withCompletion(VALID_GOAL_COMPLETION_E) + .withEndDateTime(VALID_GOAL_END_DATE_TIME).build(); + + OngoingGoalDescriptor descriptor = new OngoingGoalDescriptorBuilder().withCompletion(VALID_GOAL_COMPLETION_E) + .withEndDateTime(VALID_GOAL_END_DATE_TIME).build(); + OngoingGoalCommand ongoingGoalCommand = prepareCommand(indexLastGoal, descriptor); + + String expectedCommandException = Messages.MESSAGE_GOAL_ONGOING_ERROR; + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + expectedModel.updateGoalWithoutParameters(lastGoal, ongoingGoal); + + assertCommandFailure(ongoingGoalCommand, model, expectedCommandException); + } + + @Test + public void execute_invalidGoalIndexUnfilteredList_failure() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredGoalList().size() + 1); + OngoingGoalDescriptor descriptor = new OngoingGoalDescriptorBuilder().withCompletion(VALID_GOAL_COMPLETION_E) + .withEndDateTime(VALID_GOAL_END_DATE_TIME).build(); + OngoingGoalCommand ongoingGoalCommand = prepareCommand(outOfBoundIndex, descriptor); + + assertCommandFailure(ongoingGoalCommand, model, Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + } + + @Test + public void executeUndoRedo_invalidIndexUnfilteredList_failure() { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredGoalList().size() + 1); + OngoingGoalDescriptor descriptor = new OngoingGoalDescriptorBuilder().withCompletion(VALID_GOAL_COMPLETION_E) + .withEndDateTime(VALID_GOAL_END_DATE_TIME).build(); + OngoingGoalCommand ongoingGoalCommand = prepareCommand(outOfBoundIndex, descriptor); + + // execution failed -> ongoingGoalCommand not pushed into undoRedoStack + assertCommandFailure(ongoingGoalCommand, model, Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + + // no commands in undoRedoStack -> undoCommand and redoCommand fail + assertCommandFailure(undoCommand, model, UndoCommand.MESSAGE_FAILURE); + assertCommandFailure(redoCommand, model, RedoCommand.MESSAGE_FAILURE); + } + + @Test + public void equals() throws Exception { + final OngoingGoalCommand standardCommand = prepareCommand(INDEX_FIRST_GOAL, DESC_GOAL_COMPLETED_E); + + // same values -> returns true + OngoingGoalDescriptor copyDescriptor = new OngoingGoalDescriptor(DESC_GOAL_COMPLETED_E); + OngoingGoalCommand commandWithSameValues = prepareCommand(INDEX_FIRST_GOAL, copyDescriptor); + assertTrue(standardCommand.equals(commandWithSameValues)); + + // same object -> returns true + assertTrue(standardCommand.equals(standardCommand)); + + // null -> returns false + assertFalse(standardCommand.equals(null)); + + // different types -> returns false + assertFalse(standardCommand.equals(new ClearCommand())); + + // different index -> returns false + assertFalse(standardCommand.equals(new OngoingGoalCommand(INDEX_SECOND_GOAL, DESC_GOAL_COMPLETED_E))); + } + + /** + * Returns an {@code OngoingGoalCommand} with parameters {@code index} and {@code descriptor} + */ + private OngoingGoalCommand prepareCommand(Index index, OngoingGoalDescriptor descriptor) { + OngoingGoalCommand ongoingGoalCommand = new OngoingGoalCommand(index, descriptor); + ongoingGoalCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return ongoingGoalCommand; + } +} +``` +###### \java\seedu\address\logic\commands\SortGoalCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) and unit tests for Sort Goal Command. + */ +public class SortGoalCommandTest { + + private static final String VALID_GOAL_FIELD = "importance"; + private static final String VALID_GOAL_ORDER = "descending"; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Model model; + private Model expectedModel; + private SortGoalCommand sortGoalCommandA; + private SortGoalCommand sortGoalCommandB; + private SortGoalCommand sortGoalCommandC; + @Before + public void setUp() { + model = new ModelManager(getTypicalGoalAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + sortGoalCommandA = new SortGoalCommand(VALID_GOAL_FIELD, VALID_GOAL_ORDER); + sortGoalCommandA.setData(model, new CommandHistory(), new UndoRedoStack()); + sortGoalCommandB = new SortGoalCommand("startdatetime", "ascending"); + sortGoalCommandB.setData(model, new CommandHistory(), new UndoRedoStack()); + sortGoalCommandC = new SortGoalCommand("completion", VALID_GOAL_ORDER); + sortGoalCommandC.setData(model, new CommandHistory(), new UndoRedoStack()); + } + + + @Test + public void execute_goalListIsNotFiltered_showsSortedList() { + assertCommandSuccess(sortGoalCommandA, model, String.format(MESSAGE_SUCCESS, VALID_GOAL_FIELD, + VALID_GOAL_ORDER), expectedModel); + assertCommandSuccess(sortGoalCommandB, model, String.format(MESSAGE_SUCCESS, "startdatetime", + "ascending"), expectedModel); + assertCommandSuccess(sortGoalCommandC, model, String.format(MESSAGE_SUCCESS, "completion", + VALID_GOAL_ORDER), expectedModel); + } + + @Test + public void execute_emptyGoalList_throwsCommandException() throws Exception { + AddCommandTest.ModelStub modelStub = new ModelStubThrowingEmptyGoalListException(); + + thrown.expect(CommandException.class); + thrown.expectMessage(Messages.MESSAGE_INVALID_SORT_COMMAND_USAGE); + + getSortGoalCommandForGoal(VALID_GOAL_FIELD, VALID_GOAL_ORDER, modelStub).execute(); + } + + /** + * Generates a new SortGoalCommand with the details of the given goal. + */ + private SortGoalCommand getSortGoalCommandForGoal(String goalField, String goalOrder, Model model) { + SortGoalCommand command = new SortGoalCommand(goalField, goalOrder); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + /** + * A Model stub that always throw a EmptyGoalListException when trying to sort goal list. + */ + private class ModelStubThrowingEmptyGoalListException extends AddCommandTest.ModelStub { + @Override + public void sortGoal(String goalField, String goalOrder) throws EmptyGoalListException { + throw new EmptyGoalListException(); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } +} +``` +###### \java\seedu\address\logic\commands\ThemeCommandTest.java +``` java +public class ThemeCommandTest { + + private static final String VALID_THEME = "dark"; + + @Rule + public final EventsCollectorRule eventsCollectorRule = new EventsCollectorRule(); + + @Test + public void execute_themeSwitch_success() { + CommandResult result = new ThemeCommand(VALID_THEME).execute(); + assertEquals(MESSAGE_SUCCESS, result.feedbackToUser); + assertTrue(eventsCollectorRule.eventsCollector.getMostRecent() instanceof ThemeSwitchRequestEvent); + assertTrue(eventsCollectorRule.eventsCollector.getSize() == 1); + } + +} +``` +###### \java\seedu\address\logic\parser\AddressBookParserTest.java +``` java + @Test + public void parseCommand_addGoal_returnsTrue() throws Exception { + Goal goal = new GoalBuilder().build(); + AddGoalCommand command = (AddGoalCommand) parser.parseCommand(GoalUtil.getAddGoalCommand(goal)); + assertEquals(new AddGoalCommand(goal), command); + } + + @Test + public void parseCommand_addGoalAliasOne_returnsTrue() throws Exception { + Goal goal = new GoalBuilder().build(); + AddGoalCommand command = (AddGoalCommand) parser.parseCommand( + AddGoalCommand.COMMAND_ALIAS_1 + " " + GoalUtil.getGoalDetails(goal)); + assertEquals(new AddGoalCommand(goal), command); + } + + @Test + public void parseCommand_addGoalAliasTwo_returnsTrue() throws Exception { + Goal goal = new GoalBuilder().build(); + AddGoalCommand command = (AddGoalCommand) parser.parseCommand( + AddGoalCommand.COMMAND_ALIAS_2 + " " + GoalUtil.getGoalDetails(goal)); + assertEquals(new AddGoalCommand(goal), command); + } + + @Test + public void parseCommand_editGoal_returnsTrue() throws Exception { + Goal goal = new GoalBuilder().build(); + EditGoalDescriptor descriptor = new EditGoalDescriptorBuilder(goal).build(); + EditGoalCommand command = (EditGoalCommand) parser.parseCommand(EditGoalCommand.COMMAND_WORD + " " + + INDEX_FIRST_GOAL.getOneBased() + " " + GoalUtil.getGoalDetails(goal)); + assertEquals(new EditGoalCommand(INDEX_FIRST_GOAL, descriptor), command); + } + + @Test + public void parseCommand_editGoalAliasOne_returnsTrue() throws Exception { + Goal goal = new GoalBuilder().build(); + EditGoalDescriptor descriptor = new EditGoalDescriptorBuilder(goal).build(); + EditGoalCommand command = (EditGoalCommand) parser.parseCommand(EditGoalCommand.COMMAND_ALIAS_1 + " " + + INDEX_FIRST_GOAL.getOneBased() + " " + GoalUtil.getGoalDetails(goal)); + assertEquals(new EditGoalCommand(INDEX_FIRST_GOAL, descriptor), command); + } + + @Test + public void parseCommand_editGoalAliasTwo_returnsTrue() throws Exception { + Goal goal = new GoalBuilder().build(); + EditGoalDescriptor descriptor = new EditGoalDescriptorBuilder(goal).build(); + EditGoalCommand command = (EditGoalCommand) parser.parseCommand(EditGoalCommand.COMMAND_ALIAS_2 + " " + + INDEX_FIRST_GOAL.getOneBased() + " " + GoalUtil.getGoalDetails(goal)); + assertEquals(new EditGoalCommand(INDEX_FIRST_GOAL, descriptor), command); + } + + @Test + public void parseCommand_deleteGoal_returnsTrue() throws Exception { + DeleteGoalCommand command = (DeleteGoalCommand) parser.parseCommand( + DeleteGoalCommand.COMMAND_WORD + " " + INDEX_FIRST_GOAL.getOneBased()); + assertEquals(new DeleteGoalCommand(INDEX_FIRST_GOAL), command); + } + + @Test + public void parseCommand_deleteGoalAliasOne_returnsTrue() throws Exception { + DeleteGoalCommand command = (DeleteGoalCommand) parser.parseCommand( + DeleteGoalCommand.COMMAND_ALIAS_1 + " " + INDEX_FIRST_GOAL.getOneBased()); + assertEquals(new DeleteGoalCommand(INDEX_FIRST_GOAL), command); + } + + @Test + public void parseCommand_deleteGoalAliasTwo_returnsTrue() throws Exception { + DeleteGoalCommand command = (DeleteGoalCommand) parser.parseCommand( + DeleteGoalCommand.COMMAND_ALIAS_2 + " " + INDEX_FIRST_GOAL.getOneBased()); + assertEquals(new DeleteGoalCommand(INDEX_FIRST_GOAL), command); + } + + @Test + public void parseCommand_completeGoal_returnsTrue() throws Exception { + Goal goal = new GoalBuilder().build(); + CompleteGoalDescriptor descriptor = new CompleteGoalDescriptorBuilder(goal).build(); + CompleteGoalCommand command = (CompleteGoalCommand) parser.parseCommand( + CompleteGoalCommand.COMMAND_WORD + " " + INDEX_FIRST_GOAL.getOneBased()); + assertEquals(new CompleteGoalCommand(INDEX_FIRST_GOAL, descriptor), command); + } + + @Test + public void parseCommand_completeGoalAliasOne_returnsTrue() throws Exception { + Goal goal = new GoalBuilder().build(); + CompleteGoalDescriptor descriptor = new CompleteGoalDescriptorBuilder(goal).build(); + CompleteGoalCommand command = (CompleteGoalCommand) parser.parseCommand( + CompleteGoalCommand.COMMAND_ALIAS_1 + " " + INDEX_FIRST_GOAL.getOneBased()); + assertEquals(new CompleteGoalCommand(INDEX_FIRST_GOAL, descriptor), command); + } + + @Test + public void parseCommand_completeGoalAliasTwo_returnsTrue() throws Exception { + Goal goal = new GoalBuilder().build(); + CompleteGoalDescriptor descriptor = new CompleteGoalDescriptorBuilder(goal).build(); + CompleteGoalCommand command = (CompleteGoalCommand) parser.parseCommand( + CompleteGoalCommand.COMMAND_ALIAS_2 + " " + INDEX_FIRST_GOAL.getOneBased()); + assertEquals(new CompleteGoalCommand(INDEX_FIRST_GOAL, descriptor), command); + } + + @Test + public void parseCommand_theme_returnsTrue() throws Exception { + ThemeCommand command = (ThemeCommand) parser.parseCommand(ThemeCommand.COMMAND_WORD + " " + "dark"); + assertEquals(new ThemeCommand("dark"), command); + } + + @Test + public void parseCommand_themeAlias_returnsTrue() throws Exception { + ThemeCommand command = (ThemeCommand) parser.parseCommand(ThemeCommand.COMMAND_ALIAS + " " + "light"); + assertEquals(new ThemeCommand("light"), command); + } + + @Test + public void parseCommand_sortGoal_returnsTrue() throws Exception { + SortGoalCommand command = (SortGoalCommand) parser.parseCommand( + SortGoalCommand.COMMAND_WORD + " " + "f/importance" + " " + "o/ascending"); + assertEquals(new SortGoalCommand("importance", "ascending"), command); + } + + @Test + public void parseCommand_sortGoalAlias_returnsTrue() throws Exception { + SortGoalCommand command = (SortGoalCommand) parser.parseCommand( + SortGoalCommand.COMMAND_ALIAS + " " + "f/completion" + " " + "o/ascending"); + assertEquals(new SortGoalCommand("completion", "ascending"), command); + + command = (SortGoalCommand) parser.parseCommand( + SortGoalCommand.COMMAND_ALIAS + " " + "f/startdatetime" + " " + "o/ascending"); + assertEquals(new SortGoalCommand("startdatetime", "ascending"), command); + } + + @Test + public void parseCommand_sortCommand_returnsTrue() throws Exception { + SortCommand command = (SortCommand) parser.parseCommand( + SortCommand.COMMAND_WORD + " " + 1); + assertEquals(new SortCommand(INDEX_SORT_LEVEL_OF_FRIENDSHIP), command); + + command = (SortCommand) parser.parseCommand( + SortCommand.COMMAND_WORD + " " + 2); + assertEquals(new SortCommand(INDEX_SORT_MEET_DATE), command); + + command = (SortCommand) parser.parseCommand( + SortCommand.COMMAND_WORD + " " + 3); + assertEquals(new SortCommand(INDEX_SORT_BIRTHDAY), command); + } + + @Test + public void parseCommand_deleteMeetCommand_returnsTrue() throws Exception { + DeleteMeetCommand command = (DeleteMeetCommand) parser.parseCommand( + DeleteMeetCommand.COMMAND_WORD + " " + 1 + ); + assertEquals(new DeleteMeetCommand(INDEX_FIRST_PERSON), command); + + command = (DeleteMeetCommand) parser.parseCommand( + DeleteMeetCommand.COMMAND_ALIAS + " " + 1 + ); + assertEquals(new DeleteMeetCommand(INDEX_FIRST_PERSON), command); + } + + @Test + public void parseCommand_ongoingGoal_returnsTrue() throws Exception { + Goal goal = new GoalBuilder().build(); + OngoingGoalDescriptor descriptor = new OngoingGoalDescriptorBuilder(goal).build(); + OngoingGoalCommand command = (OngoingGoalCommand) parser.parseCommand( + OngoingGoalCommand.COMMAND_WORD + " " + INDEX_FIRST_GOAL.getOneBased()); + assertEquals(new OngoingGoalCommand(INDEX_FIRST_GOAL, descriptor), command); + } + + @Test + public void parseCommand_ongoingGoalAliasOne_returnsTrue() throws Exception { + Goal goal = new GoalBuilder().build(); + OngoingGoalDescriptor descriptor = new OngoingGoalDescriptorBuilder(goal).build(); + OngoingGoalCommand command = (OngoingGoalCommand) parser.parseCommand( + OngoingGoalCommand.COMMAND_ALIAS_1 + " " + INDEX_FIRST_GOAL.getOneBased()); + assertEquals(new OngoingGoalCommand(INDEX_FIRST_GOAL, descriptor), command); + } + + @Test + public void parseCommand_ongoingGoalAliasTwo_returnsTrue() throws Exception { + Goal goal = new GoalBuilder().build(); + OngoingGoalDescriptor descriptor = new OngoingGoalDescriptorBuilder(goal).build(); + OngoingGoalCommand command = (OngoingGoalCommand) parser.parseCommand( + OngoingGoalCommand.COMMAND_ALIAS_2 + " " + INDEX_FIRST_GOAL.getOneBased()); + assertEquals(new OngoingGoalCommand(INDEX_FIRST_GOAL, descriptor), command); + } +} +``` +###### \java\seedu\address\logic\parser\CompleteGoalCommandParserTest.java +``` java +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the CompleteGoalCommand code. For example, inputs "1" and "1 abc" take the + * same path through the CompleteGoalCommand, and therefore we test only one of them. + * The path variation for those two cases occur inside the ParserUtil, and + * therefore should be covered by the ParserUtilTest. + */ +public class CompleteGoalCommandParserTest { + + private CompleteGoalCommandParser parser = new CompleteGoalCommandParser(); + + @Test + public void parse_validArgs_returnsCompleteGoalCommand() { + CompleteGoalDescriptor completeGoalDescriptor = new CompleteGoalDescriptor(); + completeGoalDescriptor.setCompletion(new Completion(true)); + completeGoalDescriptor.setEndDateTime(new EndDateTime("today")); + assertParseSuccess(parser, "1", new CompleteGoalCommand(INDEX_FIRST_GOAL, completeGoalDescriptor)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CompleteGoalCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\logic\parser\DateTimeParserTest.java +``` java +public class DateTimeParserTest { + + private static final Optional nonEmptyLocalDateTime; + private static final Optional emptyLocalDateTime; + + static { + nonEmptyLocalDateTime = Optional.of(LocalDateTime + .of(2018, Month.JANUARY, 1, 15, 0, 0)); + emptyLocalDateTime = Optional.empty(); + } + @Test + public void parse_validArgs_success() { + Optional dateTimeParse = nattyDateAndTimeParser("1/1/2018 3pm"); + LocalDateTime aLocalDateTime = LocalDateTime.of(2018, Month.JANUARY, 1, 15, 0, + 0); + assertEquals(dateTimeParse, nonEmptyLocalDateTime); + } + + @Test + public void parse_invalidFormatArgs_failure() { + Optional dateTimeParse = nattyDateAndTimeParser("1/1/20183pm"); + assertNotEquals(dateTimeParse, nonEmptyLocalDateTime); + } + + @Test + public void parse_invalidArgs_returnsNull() { + Optional dateTimeParse = nattyDateAndTimeParser("!@!(KJEw"); + assertEquals(dateTimeParse, emptyLocalDateTime); + } +} +``` +###### \java\seedu\address\logic\parser\DeleteGoalCommandParserTest.java +``` java +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the DeleteGoalCommand code. For example, inputs "1" and "1 abc" take the + * same path through the DeleteGoalCommand, and therefore we test only one of them. + * The path variation for those two cases occur inside the ParserUtil, and + * therefore should be covered by the ParserUtilTest. + */ +public class DeleteGoalCommandParserTest { + + private DeleteGoalCommandParser parser = new DeleteGoalCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteGoalCommand() { + assertParseSuccess(parser, "1", new DeleteGoalCommand(INDEX_FIRST_GOAL)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteGoalCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\logic\parser\EditGoalCommandParserTest.java +``` java +public class EditGoalCommandParserTest { + + private static final String MESSAGE_INVALID_FORMAT = + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditGoalCommand.MESSAGE_USAGE); + + private EditGoalCommandParser parser = new EditGoalCommandParser(); + + @Test + public void parse_missingParts_failure() { + // no index specified + assertParseFailure(parser, VALID_GOAL_TEXT_A, MESSAGE_INVALID_FORMAT); + + // no field specified + assertParseFailure(parser, "1", EditGoalCommand.MESSAGE_NOT_EDITED); + + // no index and no field specified + assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT); + } + + @Test + public void parse_invalidPreamble_failure() { + // negative index + assertParseFailure(parser, "-5" + GOAL_TEXT_DESC_A, MESSAGE_INVALID_FORMAT); + + // zero index + assertParseFailure(parser, "0" + GOAL_TEXT_DESC_A, MESSAGE_INVALID_FORMAT); + + // invalid arguments being parsed as preamble + assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT); + + // invalid prefix being parsed as preamble + assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT); + } + + @Test + public void parse_invalidValue_failure() { + // invalid importance + assertParseFailure(parser, "1" + INVALID_IMPORTANCE_DESC, Importance.MESSAGE_IMPORTANCE_CONSTRAINTS); + + // invalid importance followed by valid goal text + assertParseFailure(parser, "1" + INVALID_IMPORTANCE_DESC + GOAL_TEXT_DESC_B, + Importance.MESSAGE_IMPORTANCE_CONSTRAINTS); + + // valid goal text followed by invalid importance. + assertParseFailure(parser, "1" + GOAL_TEXT_DESC_B + INVALID_IMPORTANCE_DESC, + Importance.MESSAGE_IMPORTANCE_CONSTRAINTS); + + // invalid goal text + assertParseFailure(parser, "1" + GOAL_IMPORTANCE_DESC_B + INVALID_GOAL_TEXT_DESC, + GoalText.MESSAGE_GOAL_TEXT_CONSTRAINTS); + + // valid importance followed by invalid goal text. + assertParseFailure(parser, "1" + INVALID_GOAL_TEXT_DESC + GOAL_IMPORTANCE_DESC_A, + GoalText.MESSAGE_GOAL_TEXT_CONSTRAINTS); + + // invalid importance followed by invalid goal text. Last invalid value is captured + assertParseFailure(parser, "1" + INVALID_IMPORTANCE_DESC + INVALID_GOAL_TEXT_DESC, + GoalText.MESSAGE_GOAL_TEXT_CONSTRAINTS); + + } + + @Test + public void parse_allFieldsSpecified_success() { + Index targetIndex = INDEX_SECOND_GOAL; + String userInput = targetIndex.getOneBased() + GOAL_TEXT_DESC_A + GOAL_IMPORTANCE_DESC_B; + + EditGoalDescriptor descriptor = new EditGoalDescriptorBuilder().withGoalText(VALID_GOAL_TEXT_A) + .withImportance(VALID_GOAL_IMPORTANCE_B).build(); + EditGoalCommand expectedCommand = new EditGoalCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_oneFieldSpecified_success() { + // goal text + Index targetIndex = INDEX_THIRD_GOAL; + String userInput = targetIndex.getOneBased() + GOAL_TEXT_DESC_A; + EditGoalDescriptor descriptor = new EditGoalDescriptorBuilder().withGoalText(VALID_GOAL_TEXT_A).build(); + EditGoalCommand expectedCommand = new EditGoalCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // importance + userInput = targetIndex.getOneBased() + GOAL_IMPORTANCE_DESC_A; + descriptor = new EditGoalDescriptorBuilder().withImportance(VALID_GOAL_IMPORTANCE_A).build(); + expectedCommand = new EditGoalCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_multipleRepeatedFields_acceptsLast() { + Index targetIndex = INDEX_FIRST_GOAL; + String userInput = targetIndex.getOneBased() + GOAL_TEXT_DESC_A + GOAL_IMPORTANCE_DESC_A + + GOAL_TEXT_DESC_B + GOAL_IMPORTANCE_DESC_B; + + EditGoalDescriptor descriptor = new EditGoalDescriptorBuilder().withImportance(VALID_GOAL_IMPORTANCE_B) + .withGoalText(VALID_GOAL_TEXT_B).build(); + EditGoalCommand expectedCommand = new EditGoalCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } + /* + @Test + public void parse_invalidValueFollowedByValidValue_fail() { + // no other valid values specified + Index targetIndex = INDEX_FIRST_GOAL; + String userInput = targetIndex.getOneBased() + INVALID_IMPORTANCE_DESC + GOAL_TEXT_DESC_B; + EditGoalDescriptor descriptor = new EditGoalDescriptorBuilder().withGoalText(VALID_GOAL_TEXT_B).build(); + EditGoalCommand expectedCommand = new EditGoalCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + }*/ +} +``` +###### \java\seedu\address\logic\parser\ParserUtilTest.java +``` java + @Test + public void parseBirthday_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseBirthday((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseBirthday((Optional) null)); + } + + @Test + public void parseBirthday_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseBirthday(INVALID_BIRTHDAY)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseBirthday(Optional.of(INVALID_BIRTHDAY))); + } + + @Test + public void parseBirthday_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseBirthday(Optional.empty()).isPresent()); + } + + @Test + public void parseBirthday_validValueWithoutWhitespace_returnsBirthday() throws Exception { + Birthday expectedBirthday = new Birthday(VALID_BIRTHDAY); + assertEquals(expectedBirthday, ParserUtil.parseBirthday(VALID_BIRTHDAY)); + assertEquals(Optional.of(expectedBirthday), ParserUtil.parseBirthday(Optional.of(VALID_BIRTHDAY))); + } + + @Test + public void parseBirthday_validValueWithWhitespace_returnsTrimmedBirthday() throws Exception { + String birthdayWithWhitespace = WHITESPACE + VALID_BIRTHDAY + WHITESPACE; + Birthday expectedBirthday = new Birthday(VALID_BIRTHDAY); + assertEquals(expectedBirthday, ParserUtil.parseBirthday(birthdayWithWhitespace)); + assertEquals(Optional.of(expectedBirthday), ParserUtil.parseBirthday(Optional.of(birthdayWithWhitespace))); + } + + @Test + public void parseLevelOfFriendship_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseLevelOfFriendship((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil + .parseLevelOfFriendship((Optional) null)); + } + + @Test + public void parseLevelOfFriendship_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil + .parseLevelOfFriendship(INVALID_LEVEL_OF_FRIENDSHIP)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil + .parseLevelOfFriendship(Optional.of(INVALID_LEVEL_OF_FRIENDSHIP))); + } + + @Test + public void parseLevelOfFriendship_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseLevelOfFriendship(Optional.empty()).isPresent()); + } + + @Test + public void parseLevelOfFriendship_validValueWithoutWhitespace_returnsLevelOfFriendship() throws Exception { + LevelOfFriendship expectedLevelOfFriendship = new LevelOfFriendship(VALID_LEVEL_OF_FRIENDSHIP); + assertEquals(expectedLevelOfFriendship, ParserUtil.parseLevelOfFriendship(VALID_LEVEL_OF_FRIENDSHIP)); + assertEquals(Optional.of(expectedLevelOfFriendship), ParserUtil + .parseLevelOfFriendship(Optional.of(VALID_LEVEL_OF_FRIENDSHIP))); + } + + @Test + public void parseLevelOfFriendship_validValueWithWhitespace_returnsTrimmedLevelOfFriendship() throws Exception { + String levelOfFriendshipWithWhitespace = WHITESPACE + VALID_LEVEL_OF_FRIENDSHIP + WHITESPACE; + LevelOfFriendship expectedLevelOfFriendship = new LevelOfFriendship(VALID_LEVEL_OF_FRIENDSHIP); + assertEquals(expectedLevelOfFriendship, ParserUtil.parseLevelOfFriendship(levelOfFriendshipWithWhitespace)); + assertEquals(Optional.of(expectedLevelOfFriendship), ParserUtil + .parseLevelOfFriendship(Optional.of(levelOfFriendshipWithWhitespace))); + } + + @Test + public void parseUnitNumber_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseUnitNumber((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseUnitNumber((Optional) null)); + } + + @Test + public void parseUnitNumber_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseUnitNumber(INVALID_UNIT_NUMBER)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil + .parseUnitNumber(Optional.of(INVALID_UNIT_NUMBER))); + } + + @Test + public void parseUnitNumber_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseUnitNumber(Optional.empty()).isPresent()); + } + + @Test + public void parseUnitNumber_validValueWithoutWhitespace_returnsUnitNumber() throws Exception { + UnitNumber expectedUnitNumber = new UnitNumber(VALID_UNIT_NUMBER); + assertEquals(expectedUnitNumber, ParserUtil.parseUnitNumber(VALID_UNIT_NUMBER)); + assertEquals(Optional.of(expectedUnitNumber), ParserUtil.parseUnitNumber(Optional.of(VALID_UNIT_NUMBER))); + } + + @Test + public void parseUnitNumber_validValueWithWhitespace_returnsTrimmedUnitNumber() throws Exception { + String unitNumberWithWhitespace = WHITESPACE + VALID_UNIT_NUMBER + WHITESPACE; + UnitNumber expectedUnitNumber = new UnitNumber(VALID_UNIT_NUMBER); + assertEquals(expectedUnitNumber, ParserUtil.parseUnitNumber(unitNumberWithWhitespace)); + assertEquals(Optional.of(expectedUnitNumber), ParserUtil.parseUnitNumber(Optional + .of(unitNumberWithWhitespace))); + } + + @Test + public void parseCca_null_throwsNullPointerException() throws Exception { + thrown.expect(NullPointerException.class); + ParserUtil.parseCca(null); + } + + @Test + public void parseCca_invalidValue_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parseCca(INVALID_CCA); + } + + @Test + public void parseCca_validValueWithoutWhitespace_returnsCca() throws Exception { + Cca expectedCca = new Cca(VALID_CCA_1); + assertEquals(expectedCca, ParserUtil.parseCca(VALID_CCA_1)); + } + + @Test + public void parseCca_validValueWithWhitespace_returnsTrimmedCca() throws Exception { + String ccaWithWhitespace = WHITESPACE + VALID_CCA_1 + WHITESPACE; + Cca expectedCca = new Cca(VALID_CCA_1); + assertEquals(expectedCca, ParserUtil.parseCca(ccaWithWhitespace)); + } + + @Test + public void parseCcas_null_throwsNullPointerException() throws Exception { + thrown.expect(NullPointerException.class); + ParserUtil.parseCcas(null); + } + + @Test + public void parseCcas_collectionWithInvalidCcas_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parseCcas(Arrays.asList(VALID_CCA_1, INVALID_CCA)); + } + + @Test + public void parseCcas_emptyCollection_returnsEmptySet() throws Exception { + assertTrue(ParserUtil.parseCcas(Collections.emptyList()).isEmpty()); + } + + @Test + public void parseCcas_collectionWithValidCcas_returnsCcaSet() throws Exception { + Set actualCcaSet = ParserUtil.parseCcas(Arrays.asList(VALID_CCA_1, VALID_CCA_2)); + Set expectedCcaSet = new HashSet(Arrays.asList(new Cca(VALID_CCA_1), new Cca(VALID_CCA_2))); + + assertEquals(expectedCcaSet, actualCcaSet); + } + + @Test + public void parseSortGoalField_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseSortGoalField((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseSortGoalField((Optional) null)); + } + + @Test + public void parseSortGoalField_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseSortGoalField(INVALID_SORT_GOAL_FIELD)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseSortGoalField(Optional + .of(INVALID_SORT_GOAL_FIELD))); + } + + @Test + public void parseSortGoalField_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseSortGoalField(Optional.empty()).isPresent()); + } + + @Test + public void parseSortGoalField_validValueWithoutWhitespace_returnsGoalFieldString() throws Exception { + assertEquals(VALID_SORT_GOAL_FIELD, ParserUtil.parseSortGoalField(VALID_SORT_GOAL_FIELD)); + assertEquals(Optional.of(VALID_SORT_GOAL_FIELD), ParserUtil.parseSortGoalField(Optional + .of(VALID_SORT_GOAL_FIELD))); + } + + @Test + public void parseSortGoalField_validValueWithWhitespace_returnsTrimmedGoalFieldString() throws Exception { + String goalFieldWithWhitespace = WHITESPACE + VALID_SORT_GOAL_FIELD + WHITESPACE; + assertEquals(VALID_SORT_GOAL_FIELD, ParserUtil.parseSortGoalField(goalFieldWithWhitespace)); + assertEquals(Optional.of(VALID_SORT_GOAL_FIELD), ParserUtil.parseSortGoalField(Optional + .of(goalFieldWithWhitespace))); + } + + @Test + public void parseSortGoalOrder_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseSortGoalOrder((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseSortGoalOrder((Optional) null)); + } + + @Test + public void parseSortGoalOrder_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseSortGoalOrder(INVALID_SORT_GOAL_ORDER)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseSortGoalOrder(Optional + .of(INVALID_SORT_GOAL_ORDER))); + } + + @Test + public void parseSortGoalOrder_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseSortGoalOrder(Optional.empty()).isPresent()); + } + + @Test + public void parseSortGoalOrder_validValueWithoutWhitespace_returnsGoalOrderString() throws Exception { + assertEquals(VALID_SORT_GOAL_ORDER, ParserUtil.parseSortGoalOrder(VALID_SORT_GOAL_ORDER)); + assertEquals(Optional.of(VALID_SORT_GOAL_ORDER), ParserUtil.parseSortGoalOrder(Optional + .of(VALID_SORT_GOAL_ORDER))); + } + + @Test + public void parseSortGoalOrder_validValueWithWhitespace_returnsTrimmedGoalOrderString() throws Exception { + String goalOrderWithWhitespace = WHITESPACE + VALID_SORT_GOAL_ORDER + WHITESPACE; + assertEquals(VALID_SORT_GOAL_ORDER, ParserUtil.parseSortGoalOrder(goalOrderWithWhitespace)); + assertEquals(Optional.of(VALID_SORT_GOAL_ORDER), ParserUtil.parseSortGoalOrder(Optional + .of(goalOrderWithWhitespace))); + } +``` +###### \java\seedu\address\logic\parser\SortGoalCommandParserTest.java +``` java +public class SortGoalCommandParserTest { + private SortGoalCommandParser parser = new SortGoalCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + GOAL_SORT_FIELD_DESC_B + GOAL_SORT_ORDER_DESC_B, + new SortGoalCommand(VALID_GOAL_SORT_FIELD_B, VALID_GOAL_SORT_ORDER_B)); + + // multiple goal sort fields - last sort goal field accepted + assertParseSuccess(parser, GOAL_SORT_FIELD_DESC_A + GOAL_SORT_FIELD_DESC_B + GOAL_SORT_ORDER_DESC_B, + new SortGoalCommand(VALID_GOAL_SORT_FIELD_B, VALID_GOAL_SORT_ORDER_B)); + + // multiple goal sort order - last sort goal order accepted + assertParseSuccess(parser, GOAL_SORT_FIELD_DESC_B + GOAL_SORT_ORDER_DESC_A + GOAL_SORT_ORDER_DESC_B, + new SortGoalCommand(VALID_GOAL_SORT_FIELD_B, VALID_GOAL_SORT_ORDER_B)); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortGoalCommand.MESSAGE_USAGE); + + // missing sort goal field prefix + assertParseFailure(parser, GOAL_SORT_ORDER_DESC_B, expectedMessage); + + // missing sort goal order prefix + assertParseFailure(parser, GOAL_SORT_FIELD_DESC_B, expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortGoalCommand.MESSAGE_USAGE); + // invalid sort goal field + assertParseFailure(parser, INVALID_GOAL_SORT_FIELD + GOAL_SORT_ORDER_DESC_B, + expectedMessage); + + // invalid sort goal order + assertParseFailure(parser, INVALID_GOAL_SORT_ORDER + GOAL_SORT_FIELD_DESC_B, + expectedMessage); + + // non-empty preamble + assertParseFailure(parser, PREAMBLE_NON_EMPTY + GOAL_SORT_ORDER_DESC_B + GOAL_SORT_FIELD_DESC_B, + expectedMessage); + } +} +``` +###### \java\seedu\address\logic\parser\ThemeCommandParserTest.java +``` java + +public class ThemeCommandParserTest { + private ThemeCommandParser parser = new ThemeCommandParser(); + + @Test + public void parse_validArgs_returnsThemeCommand() { + assertParseSuccess(parser, "light", new ThemeCommand("light")); + } + + @Test + public void parser_invalidArgs_throwsParseException() { + assertParseFailure(parser, "invalid", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ThemeCommand.MESSAGE_INVALID_THEME_COLOUR)); + + assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ThemeCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\model\goal\GoalTextTest.java +``` java +public class GoalTextTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new GoalText(null)); + } + + @Test + public void constructor_invalidGoalText_throwsIllegalArgumentException() { + String invalidGoalText = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new GoalText(invalidGoalText)); + } + + @Test + public void isValidGoalText() { + // null goal text + Assert.assertThrows(NullPointerException.class, () -> GoalText.isValidGoalText(null)); + + // blank goal text + assertFalse(GoalText.isValidGoalText("")); // empty string + assertFalse(GoalText.isValidGoalText(" ")); // spaces only + + // valid goal text + assertTrue(GoalText.isValidGoalText("1")); + assertTrue(GoalText.isValidGoalText("aaa0")); // alphanumerical + assertTrue(GoalText.isValidGoalText("!@$#()_+")); // symbols only + assertTrue(GoalText.isValidGoalText("-1.122ewk:!@|!+@!*~")); // all kinds of symbols and alphanumerical + assertTrue(GoalText.isValidGoalText("! 1 wq ")); // with spaces + assertTrue(GoalText.isValidGoalText(" 7")); // spaces with a value + } +} +``` +###### \java\seedu\address\model\goal\ImportanceTest.java +``` java +public class ImportanceTest { + + private final Importance importanceObjectOne = new Importance("10"); + private final Importance importanceObjectTwo = new Importance("1"); + private final Importance importanceObjectThree = new Importance("10"); + + + @Test + public void importanceCompareTo_testEquals_success() { + int result = importanceObjectOne.compareTo(importanceObjectThree); + assertTrue(result == 0); + } + + @Test + public void importanceCompareTo_testGreaterThan_success() { + int result = importanceObjectOne.compareTo(importanceObjectTwo); + assertTrue(result == 1); + } + + @Test + public void importanceCompareTo_testLessThan_success() { + int result = importanceObjectTwo.compareTo(importanceObjectThree); + assertTrue(result == -1); + } + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Importance(null)); + } + + @Test + public void constructor_invalidImportance_throwsIllegalArgumentException() { + String invalidImportance = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Importance(invalidImportance)); + } + + @Test + public void isValidImportance() { + // null importance + Assert.assertThrows(NullPointerException.class, () -> Importance.isValidImportance(null)); + + // blank importance + assertFalse(Importance.isValidImportance("")); // empty string + assertFalse(Importance.isValidImportance(" ")); // spaces only + + // invalid parts + assertFalse(Importance.isValidImportance("22")); // invalid positive level of friendship + assertFalse(Importance.isValidImportance("-1")); // invalid negative level of friendship + assertFalse(Importance.isValidImportance("11")); // invalid positive level of friendship + assertFalse(Importance.isValidImportance("a")); // invalid character + assertFalse(Importance.isValidImportance("11a")); // invalid extra character and number + assertFalse(Importance.isValidImportance("10b")); // invalid extra character + assertFalse(Importance.isValidImportance("9*")); // '*' symbol + assertFalse(Importance.isValidImportance("^")); // '^' symbol + assertFalse(Importance.isValidImportance("0")); // invalid number + assertFalse(Importance.isValidImportance("1.1")); // number in decimal + + // valid importance + assertTrue(Importance.isValidImportance("1")); + assertTrue(Importance.isValidImportance("10")); // minimal + assertTrue(Importance.isValidImportance("2")); // alphabets only + assertTrue(Importance.isValidImportance("5")); // special characters local part + assertTrue(Importance.isValidImportance("7")); // numeric local part and domain name + } +} +``` +###### \java\seedu\address\model\goal\StartDateTimeTest.java +``` java +public class StartDateTimeTest { + + private final StartDateTime startDateTimeObjectOne = new StartDateTime("Date: 18 April 2018, Time: 20:20"); + private final StartDateTime startDateTimeObjectTwo = new StartDateTime("Date: 17 April 2018, Time: 20:20"); + private final StartDateTime startDateTimeObjectThree = new StartDateTime("Date: 17 April 2018, Time: 20:20"); + + + @Test + public void startDateTimeCompareTo_testGreaterThan_success() { + int result = startDateTimeObjectOne.compareTo(startDateTimeObjectTwo); + assertTrue(result == 1); + } + + @Test + public void startDateTimeCompareTo_testLessThan_success() { + int result = startDateTimeObjectThree.compareTo(startDateTimeObjectOne); + assertTrue(result == -1); + } +} +``` +###### \java\seedu\address\model\person\BirthdayTest.java +``` java +public class BirthdayTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Birthday(null)); + } + + @Test + public void constructor_invalidBirthday_throwsIllegalArgumentException() { + String invalidBirthday = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Birthday(invalidBirthday)); + } + + @Test + public void isValidBirthday() { + // null birthday + Assert.assertThrows(NullPointerException.class, () -> Birthday.isValidBirthday(null)); + + // blank birthday + assertFalse(Birthday.isValidBirthday("")); // empty string + assertFalse(Birthday.isValidBirthday(" ")); // spaces only + + // missing parts + assertFalse(Birthday.isValidBirthday("12--1997")); // missing month part + assertFalse(Birthday.isValidBirthday("--12-1998")); // missing date part + assertFalse(Birthday.isValidBirthday("//12/1998")); // missing date part + assertFalse(Birthday.isValidBirthday("12-12-")); // missing year part + assertFalse(Birthday.isValidBirthday("12/12/")); // missing year part + + + // invalid parts + assertFalse(Birthday.isValidBirthday("32-Jan-2000")); // invalid day + assertFalse(Birthday.isValidBirthday("33.01.2000")); // invalid day + assertFalse(Birthday.isValidBirthday("20/20/2000")); // invalid month + assertFalse(Birthday.isValidBirthday("20/13/1997")); // invalid month + assertFalse(Birthday.isValidBirthday("29/Feb/2001")); // invalid due to leap year + assertFalse(Birthday.isValidBirthday("31/04/2000")); // invalid day for month of April + assertFalse(Birthday.isValidBirthday("31/Sep/2000")); // invalid day for month of September + assertFalse(Birthday.isValidBirthday("31//01/2000")); // invalid birthday format + assertFalse(Birthday.isValidBirthday("31..01..2000")); // invalid birthday format + assertFalse(Birthday.isValidBirthday("20--2-1997")); // invalid birthday format + assertFalse(Birthday.isValidBirthday("20*2*1997")); // invalid symbols + assertFalse(Birthday.isValidBirthday("12 / 12 / 2012")); // contains spaces + assertFalse(Birthday.isValidBirthday("01/Jan/2000")); // using / + assertFalse(Birthday.isValidBirthday("31.Jan.2000")); // using . + assertFalse(Birthday.isValidBirthday("01-12-2000")); // using - + assertFalse(Birthday.isValidBirthday("28-Feb-2001")); + assertFalse(Birthday.isValidBirthday("28/Feb/9999")); //birthday later than today's date + // valid birthday + assertTrue(Birthday.isValidBirthday("01/01/2000")); + } +} +``` +###### \java\seedu\address\model\person\CcaTest.java +``` java +public class CcaTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Cca(null)); + } + + @Test + public void constructor_invalidCcaName_throwsIllegalArgumentException() { + String invalidCcaName = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Cca(invalidCcaName)); + } + + @Test + public void isValidCcaName() { + // null cca name + Assert.assertThrows(NullPointerException.class, () -> Cca.isValidCcaName(null)); + + // invalid cca name + assertFalse(Cca.isValidCcaName("!3")); // contains '!' + assertFalse(Cca.isValidCcaName("abc%")); // contains '%' + assertFalse(Cca.isValidCcaName("abc-1")); // contains '-' + assertFalse(Cca.isValidCcaName("abc@@@1")); // contains '@' + + // valid cca name + assertTrue(Cca.isValidCcaName("Hackathon")); // alphabets + assertTrue(Cca.isValidCcaName("Walkathon 2018")); // using .alphanumeric with spaces + assertTrue(Cca.isValidCcaName("Basketball")); // valid alphabets + assertTrue(Cca.isValidCcaName("Hackathon2018")); //alphanumeric + } +} +``` +###### \java\seedu\address\model\person\LevelOfFriendshipTest.java +``` java +public class LevelOfFriendshipTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new LevelOfFriendship(null)); + } + + @Test + public void constructor_invalidLevelOfFriendship_throwsIllegalArgumentException() { + String invalidLevelOfFriendship = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new LevelOfFriendship(invalidLevelOfFriendship)); + } + + @Test + public void isValidLevelOfFriendship() { + // null level of friendship + Assert.assertThrows(NullPointerException.class, () -> LevelOfFriendship.isValidLevelOfFriendship(null)); + + // blank level of friendship + assertFalse(LevelOfFriendship.isValidLevelOfFriendship("")); // empty string + assertFalse(LevelOfFriendship.isValidLevelOfFriendship(" ")); // spaces only + + // invalid parts + assertFalse(LevelOfFriendship.isValidLevelOfFriendship("22")); // invalid positive level of friendship + assertFalse(LevelOfFriendship.isValidLevelOfFriendship("-1")); // invalid negative level of friendship + assertFalse(LevelOfFriendship.isValidLevelOfFriendship("11")); // invalid positive level of friendship + assertFalse(LevelOfFriendship.isValidLevelOfFriendship("a")); // invalid character + assertFalse(LevelOfFriendship.isValidLevelOfFriendship("11a")); // invalid extra character and number + assertFalse(LevelOfFriendship.isValidLevelOfFriendship("10b")); // invalid extra character + assertFalse(LevelOfFriendship.isValidLevelOfFriendship("9*")); // '*' symbol + assertFalse(LevelOfFriendship.isValidLevelOfFriendship("^")); // '^' symbol + assertFalse(LevelOfFriendship.isValidLevelOfFriendship("0")); // invalid number + assertFalse(LevelOfFriendship.isValidLevelOfFriendship("1.1")); // number in decimal + + // valid level of friendship + assertTrue(LevelOfFriendship.isValidLevelOfFriendship("1")); + assertTrue(LevelOfFriendship.isValidLevelOfFriendship("10")); // minimal + assertTrue(LevelOfFriendship.isValidLevelOfFriendship("2")); // alphabets only + assertTrue(LevelOfFriendship.isValidLevelOfFriendship("5")); // special characters local part + assertTrue(LevelOfFriendship.isValidLevelOfFriendship("7")); // numeric local part and domain name + } +} +``` +###### \java\seedu\address\model\person\UniqueCcaListTest.java +``` java +public class UniqueCcaListTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void asObservableList_modifyList_throwsUnsupportedOperationException() { + UniqueCcaList uniqueCcaList = new UniqueCcaList(); + thrown.expect(UnsupportedOperationException.class); + uniqueCcaList.asObservableList().remove(0); + } +} + +``` +###### \java\seedu\address\model\person\UnitNumberTest.java +``` java +public class UnitNumberTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new UnitNumber(null)); + } + + @Test + public void constructor_invalidUnitNumber_throwsIllegalArgumentException() { + String invalidUnitNumber = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new UnitNumber(invalidUnitNumber)); + } + + @Test + public void isValidUnitNumber() { + // null unit number + Assert.assertThrows(NullPointerException.class, () -> UnitNumber.isValidUnitNumber(null)); + + // invalid unit numbers + assertFalse(UnitNumber.isValidUnitNumber("")); // empty string + assertFalse(UnitNumber.isValidUnitNumber(" ")); // spaces only + assertFalse(UnitNumber.isValidUnitNumber("#12222-1312414")); // long unit number + assertFalse(UnitNumber.isValidUnitNumber("#1-1")); // one character only + + // valid unit numbers + assertTrue(UnitNumber.isValidUnitNumber("#01-355")); + assertTrue(UnitNumber.isValidUnitNumber("#1-12")); + assertTrue(UnitNumber.isValidUnitNumber("#12-12")); + + } +} +``` +###### \java\seedu\address\model\UniqueGoalListTest.java +``` java +public class UniqueGoalListTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void asObservableList_modifyList_throwsUnsupportedOperationException() { + UniqueGoalList uniquePersonList = new UniqueGoalList(); + thrown.expect(UnsupportedOperationException.class); + uniquePersonList.asObservableList().remove(0); + } +} +``` +###### \java\seedu\address\storage\XmlAdaptedGoalTest.java +``` java +public class XmlAdaptedGoalTest { + private static final String INVALID_IMPORTANCE = "11"; + private static final String INVALID_GOAL_TEXT = " "; + + private static final String VALID_IMPORTANCE = GOAL_B.getImportance().toString(); + private static final String VALID_GOAL_TEXT = GOAL_B.getGoalText().toString(); + private static final String VALID_START_DATE_TIME = GOAL_B.getStartDateTime().toString(); + private static final String VALID_COMPLETION = GOAL_B.getCompletion().toString(); + private static final String VALID_END_DATE_TIME = GOAL_B.getEndDateTime().toString(); + + @Test + public void toModelType_validGoalDetails_returnsGoal() throws Exception { + XmlAdaptedGoal goal = new XmlAdaptedGoal(GOAL_B); + assertEquals(GOAL_B, goal.toModelType()); + } + + @Test + public void toModelType_invalidImportance_throwsIllegalValueException() { + XmlAdaptedGoal goal = + new XmlAdaptedGoal(INVALID_IMPORTANCE, VALID_GOAL_TEXT, VALID_START_DATE_TIME, VALID_END_DATE_TIME, + VALID_COMPLETION); + String expectedMessage = Importance.MESSAGE_IMPORTANCE_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, goal::toModelType); + } + + @Test + public void toModelType_nullImportance_throwsIllegalValueException() { + XmlAdaptedGoal goal = new XmlAdaptedGoal(null, VALID_GOAL_TEXT, VALID_START_DATE_TIME, + VALID_END_DATE_TIME, VALID_COMPLETION); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Importance.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, goal::toModelType); + } + + @Test + public void toModelType_invalidGoalText_throwsIllegalValueException() { + XmlAdaptedGoal goal = + new XmlAdaptedGoal(VALID_IMPORTANCE, INVALID_GOAL_TEXT, VALID_START_DATE_TIME, VALID_END_DATE_TIME, + VALID_COMPLETION); + String expectedMessage = GoalText.MESSAGE_GOAL_TEXT_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, goal::toModelType); + } + + @Test + public void toModelType_nullGoalText_throwsIllegalValueException() { + XmlAdaptedGoal goal = new XmlAdaptedGoal(VALID_IMPORTANCE, null, VALID_START_DATE_TIME, VALID_END_DATE_TIME, + VALID_COMPLETION); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, GoalText.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, goal::toModelType); + } + + + @Test + public void toModelType_nullStartDateTime_throwsIllegalValueException() { + XmlAdaptedGoal goal = new XmlAdaptedGoal(VALID_IMPORTANCE, VALID_GOAL_TEXT, null, VALID_END_DATE_TIME, + VALID_COMPLETION); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, StartDateTime.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, goal::toModelType); + } + + @Test + public void toModelType_nullCompletion_throwsIllegalValueException() { + XmlAdaptedGoal goal = new XmlAdaptedGoal(VALID_IMPORTANCE, VALID_GOAL_TEXT, VALID_START_DATE_TIME, + VALID_END_DATE_TIME, null); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Completion.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, goal::toModelType); + } + + @Test + public void toModelType_nullEndDateTime_throwsIllegalValueException() { + XmlAdaptedGoal goal = new XmlAdaptedGoal(VALID_IMPORTANCE, VALID_GOAL_TEXT, VALID_START_DATE_TIME, null, + VALID_COMPLETION); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, EndDateTime.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, goal::toModelType); + } +} +``` +###### \java\seedu\address\storage\XmlSerializableAddressBookTest.java +``` java + @Test + public void toModelType_invalidGoalFile_throwsIllegalValueException() throws Exception { + XmlSerializableAddressBook dataFromFile = XmlUtil.getDataFromFile(INVALID_GOAL_FILE, + XmlSerializableAddressBook.class); + thrown.expect(IllegalValueException.class); + dataFromFile.toModelType(); + } + + @Test + public void toModelType_typicalGoalsFile_success() throws Exception { + XmlSerializableAddressBook dataFromFile = XmlUtil.getDataFromFile(TYPICAL_GOALS_FILE, + XmlSerializableAddressBook.class); + AddressBook addressBookFromFile = dataFromFile.toModelType(); + AddressBook typicalPersonsAddressBook = TypicalGoals.getTypicalGoalAddressBook(); + assertEquals(addressBookFromFile, typicalPersonsAddressBook); + } +} +``` +###### \java\seedu\address\testutil\CompleteGoalDescriptorBuilder.java +``` java +/** + * A utility class to help with building CompleteGoalDescriptor objects. + */ +public class CompleteGoalDescriptorBuilder { + + private CompleteGoalDescriptor descriptor; + + public CompleteGoalDescriptorBuilder() { + descriptor = new CompleteGoalDescriptor(); + } + + public CompleteGoalDescriptorBuilder(CompleteGoalDescriptor descriptor) { + this.descriptor = new CompleteGoalDescriptor(descriptor); + } + + /** + * Returns an {@code CompleteGoalDescriptor} with fields containing {@code goal}'s details + */ + public CompleteGoalDescriptorBuilder(Goal goal) { + descriptor = new CompleteGoalDescriptor(); + descriptor.setCompletion(new Completion(true)); + descriptor.setEndDateTime(new EndDateTime(properDateTimeFormat(LocalDateTime.now()))); + } + + /** + * Sets the {@code Completion} of the {@code CompleteGoalDescriptor} that we are building. + */ + public CompleteGoalDescriptorBuilder withCompletion(boolean isCompleted) { + descriptor.setCompletion(new Completion(isCompleted)); + return this; + } + + /** + * Sets the {@code EndDateTime} of the {@code CompleteGoalDescriptor} that we are building. + */ + public CompleteGoalDescriptorBuilder withEndDateTime(String endDateTime) { + descriptor.setEndDateTime(new EndDateTime(endDateTime)); + return this; + } + + public CompleteGoalDescriptor build() { + return descriptor; + } +} +``` +###### \java\seedu\address\testutil\EditGoalDescriptorBuilder.java +``` java +/** + * A utility class to help with building EditGoalDescriptor objects. + */ +public class EditGoalDescriptorBuilder { + + private EditGoalDescriptor descriptor; + + public EditGoalDescriptorBuilder() { + descriptor = new EditGoalDescriptor(); + } + + public EditGoalDescriptorBuilder(EditGoalDescriptor descriptor) { + this.descriptor = new EditGoalDescriptor(descriptor); + } + + /** + * Returns an {@code EditGoalDescriptor} with fields containing {@code goal}'s details + */ + public EditGoalDescriptorBuilder(Goal goal) { + descriptor = new EditGoalDescriptor(); + descriptor.setGoalText(goal.getGoalText()); + descriptor.setImportance(goal.getImportance()); + } + + /** + * Sets the {@code GoalText} of the {@code EditGoalDescriptor} that we are building. + */ + public EditGoalDescriptorBuilder withGoalText(String goalText) { + descriptor.setGoalText(new GoalText(goalText)); + return this; + } + + /** + * Sets the {@code Importance} of the {@code EditGoalDescriptor} that we are building. + */ + public EditGoalDescriptorBuilder withImportance(String importance) { + descriptor.setImportance(new Importance(importance)); + return this; + } + + public EditGoalDescriptor build() { + return descriptor; + } +} + +``` +###### \java\seedu\address\testutil\GoalBuilder.java +``` java +/** + * A utility class to help with building Person objects. + */ +public class GoalBuilder { + + public static final boolean DEFAULT_COMPLETION = false; + public static final String DEFAULT_EMPTY_END_DATE_TIME = ""; + public static final String DEFAULT_END_DATE_TIME = "today"; + public static final String DEFAULT_GOAL_TEXT = "er yea acadamic no la no la"; + public static final String DEFAULT_IMPORTANCE = "1"; + public static final String DEFAULT_START_DATE_TIME = "2017-04-08 12:30"; + + private Completion completion; + private EndDateTime endDateTime; + private GoalText goalText; + private Importance importance; + private StartDateTime startDateTime; + + public GoalBuilder() { + completion = new Completion(DEFAULT_COMPLETION); + endDateTime = new EndDateTime(DEFAULT_EMPTY_END_DATE_TIME); + goalText = new GoalText(DEFAULT_GOAL_TEXT); + importance = new Importance(DEFAULT_IMPORTANCE); + startDateTime = new StartDateTime(getLocalDateTimeFromString(DEFAULT_START_DATE_TIME)); + } + + public GoalBuilder(boolean isCompleted) { + completion = new Completion(isCompleted); + endDateTime = new EndDateTime(DEFAULT_END_DATE_TIME); + goalText = new GoalText(DEFAULT_GOAL_TEXT); + importance = new Importance(DEFAULT_IMPORTANCE); + startDateTime = new StartDateTime(getLocalDateTimeFromString(DEFAULT_START_DATE_TIME)); + } + + /** + * Initializes the GoalBuilder with the data of {@code goalToCopy}. + */ + public GoalBuilder(Goal goalToCopy) { + completion = goalToCopy.getCompletion(); + endDateTime = goalToCopy.getEndDateTime(); + goalText = goalToCopy.getGoalText(); + importance = goalToCopy.getImportance(); + startDateTime = goalToCopy.getStartDateTime(); + } + + /** + * Sets the {@code Completion} of the {@code Goal} that we are building. + */ + public GoalBuilder withCompletion(Boolean completion) { + this.completion = new Completion(completion); + return this; + } + + /** + * Sets the {@code EndDateTime} of the {@code Goal} that we are building. + */ + public GoalBuilder withEndDateTime(String endDateTime) { + this.endDateTime = new EndDateTime(endDateTime); + return this; + } + + /** + * Sets the {@code GoalText} of the {@code Goal} that we are building. + */ + public GoalBuilder withGoalText(String goalText) { + this.goalText = new GoalText(goalText); + return this; + } + + /** + * Sets the {@code Importance} of the {@code Goal} that we are building. + */ + public GoalBuilder withImportance(String importance) { + this.importance = new Importance(importance); + return this; + } + + /** + * Sets the {@code StartDateTime} of the {@code Goal} that we are building. + */ + public GoalBuilder withStartDateTime(String startDateTime) { + this.startDateTime = new StartDateTime(getLocalDateTimeFromString(startDateTime)); + return this; + } + + public Goal build() { + return new Goal(importance, goalText, startDateTime, endDateTime, completion); + } + +} + +``` +###### \java\seedu\address\testutil\GoalUtil.java +``` java +/** + * A utility class for Goal. + */ +public class GoalUtil { + + /** + * Returns an add goal command string for adding the {@code goal}. + */ + public static String getAddGoalCommand(Goal goal) { + return AddGoalCommand.COMMAND_WORD + " " + getGoalDetails(goal); + } + + /** + * Returns the part of command string for the given {@code goal}'s details. + */ + public static String getGoalDetails(Goal goal) { + StringBuilder sb = new StringBuilder(); + sb.append(PREFIX_IMPORTANCE + goal.getImportance().value + " "); + sb.append(PREFIX_GOAL_TEXT + goal.getGoalText().value + " "); + return sb.toString(); + } + + public static int getGoalCompletion(ObservableList goalList) { + int totalGoal = goalList.size(); + int totalGoalCompleted = 0; + String completionStatus; + for (int i = 0; i < totalGoal; i++) { + completionStatus = goalList.get(i).getCompletion().value; + totalGoalCompleted += isCompletedGoal(completionStatus); + } + int percentageGoalCompletion = (int) (((float) totalGoalCompleted / totalGoal) * PERCENTAGE_KEY_NUMBER); + return percentageGoalCompletion; + } + + /** + * @param completionStatus gives a String that should be either "true" or "false", indicating if the goal is + * completed. + * @return true or false + */ + private static int isCompletedGoal(String completionStatus) { + int valueToAdd; + if (completionStatus.equals("true")) { + valueToAdd = 1; + } else { + valueToAdd = 0; + } + return valueToAdd; + } +} +``` +###### \java\seedu\address\testutil\OngoingGoalDescriptorBuilder.java +``` java +/** + * A utility class to help with building OngoingGoalDescriptor objects. + */ +public class OngoingGoalDescriptorBuilder { + + private OngoingGoalDescriptor descriptor; + + public OngoingGoalDescriptorBuilder() { + descriptor = new OngoingGoalDescriptor(); + } + + public OngoingGoalDescriptorBuilder(OngoingGoalDescriptor descriptor) { + this.descriptor = new OngoingGoalDescriptor(descriptor); + } + + /** + * Returns an {@code OngoingGoalDescriptor} with fields containing {@code goal}'s details + */ + public OngoingGoalDescriptorBuilder(Goal goal) { + descriptor = new OngoingGoalDescriptor(); + descriptor.setCompletion(new Completion(false)); + descriptor.setEndDateTime(new EndDateTime("")); + } + + /** + * Sets the {@code Completion} of the {@code OngoingGoalDescriptor} that we are building. + */ + public OngoingGoalDescriptorBuilder withCompletion(boolean isOngoing) { + descriptor.setCompletion(new Completion(isOngoing)); + return this; + } + + /** + * Sets the {@code EndDateTime} of the {@code OngoingGoalDescriptor} that we are building. + */ + public OngoingGoalDescriptorBuilder withEndDateTime(String endDateTime) { + descriptor.setEndDateTime(new EndDateTime(endDateTime)); + return this; + } + + public OngoingGoalDescriptor build() { + return descriptor; + } +} +``` +###### \java\seedu\address\testutil\TypicalGoals.java +``` java +/** + * A utility class containing a list of {@code Goal} objects to be used in tests. + */ +public class TypicalGoals { + + public static final Goal GOAL_A = new GoalBuilder().withCompletion(false) + .withEndDateTime("").withGoalText("stay fit").withImportance("1") + .withStartDateTime("2017-04-08 12:30").build(); + public static final Goal GOAL_B = new GoalBuilder().withCompletion(false) + .withEndDateTime("").withGoalText("eat fruits daily").withImportance("2") + .withStartDateTime("2017-05-08 12:30").build(); + public static final Goal GOAL_C = new GoalBuilder().withCompletion(true).withEndDateTime("2018-04-08 12:30") + .withGoalText("aa").withImportance("7").withStartDateTime("2017-06-08 12:30").build(); + public static final Goal GOAL_D = new GoalBuilder().withCompletion(true).withEndDateTime("2018-04-08 12:31") + .withGoalText("bb").withImportance("4").withStartDateTime("2017-06-08 12:31").build(); + public static final Goal GOAL_E = new GoalBuilder().withCompletion(false).withEndDateTime("") + .withGoalText("cc").withImportance("10").withStartDateTime("2017-06-08 12:32") + .build(); + public static final Goal GOAL_F = new GoalBuilder().withCompletion(false).withEndDateTime("") + .withGoalText("dd").withImportance("3").withStartDateTime("2017-06-08 12:33").build(); + public static final Goal GOAL_G = new GoalBuilder().withCompletion(false).withEndDateTime("") + .withGoalText("ee").withImportance("8").withStartDateTime("2017-06-08 12:35") + .build(); + + // Manually added + public static final Goal HOON = new GoalBuilder().withCompletion(false).withEndDateTime("") + .withGoalText("ff").withImportance("1").withStartDateTime("2017-06-08 12:36") + .build(); + public static final Goal IDA = new GoalBuilder().withCompletion(false).withEndDateTime("") + .withGoalText("gg").withImportance("3").withStartDateTime("2017-06-08 12:38") + .build(); + public static final Goal JAKE = new GoalBuilder().withCompletion(false).withEndDateTime("") + .withGoalText("hii").withImportance("3").withStartDateTime("2018-04-08 12:30") + .build(); + + // Manually added - Goal's details found in {@code GoalCommandTestUtil} + public static final Goal GOAL_A1 = new GoalBuilder().withCompletion(VALID_GOAL_COMPLETION_A) + .withEndDateTime(VALID_GOAL_END_DATE_TIME_STRING_A) + .withGoalText(VALID_GOAL_TEXT_A).withImportance(VALID_GOAL_IMPORTANCE_A) + .withStartDateTime(VALID_GOAL_START_DATE_TIME_STRING_A).build(); + public static final Goal GOAL_A2 = new GoalBuilder().withCompletion(VALID_GOAL_COMPLETION_B) + .withEndDateTime(VALID_GOAL_END_DATE_TIME_STRING_B) + .withGoalText(VALID_GOAL_TEXT_B).withImportance(VALID_GOAL_IMPORTANCE_B) + .withStartDateTime(VALID_GOAL_START_DATE_TIME_STRING_B).build(); + + private TypicalGoals() {} // prevents instantiation + + /** + * Returns an {@code AddressBook} with all the typical goals. + */ + public static AddressBook getTypicalGoalAddressBook() { + AddressBook ab = new AddressBook(); + for (Goal goal : getTypicalGoals()) { + try { + ab.addGoal(goal); + } catch (DuplicateGoalException e) { + throw new AssertionError("not possible"); + } + } + return ab; + } + + public static List getTypicalGoals() { + return new ArrayList<>(Arrays.asList(GOAL_A, GOAL_B, GOAL_C, GOAL_D, GOAL_E, GOAL_F, GOAL_G)); + } +} +``` +###### \java\seedu\address\ui\testutil\GuiTestAssert.java +``` java + /** + * Returns the color style for {@code tagName}'s label. The tag's color is determined by looking up the color + * in {@code PersonCard#TAG_COLOR_STYLES}, using an index generated by the hash code of the tag's content. + * + * @see PersonCard#getTagColorStyleFor(String) + */ + private static String getTagColorStyleFor(String tagName) { + switch (tagName) { + case "classmates": + case "owesMoney": + return "teal"; + + case "colleagues": + case "neighbours": + case "bff": + return "yellow"; + + case "family": + case "friend": + case "closefriend": + return "orange"; + + case "friends": + case "classmate": + return "brown"; + + case "RA": + return "pink"; + + case "husband": + case "cousin": + return "purple"; + + case "boyfriend": + return "green"; + + case "schoolmate": + return "blue"; + + case "exgirlfriend": + case "malafriend": + return "red"; + default: + fail(tagName + " does not have a color assigned."); + return ""; + } + } + + /** + * Asserts that the tags in {@code actualCard} matches all the tags in {@code expectedPerson} with the correct + * color. + */ + private static void assertTagsEqual(Person expectedPerson, PersonCardHandle actualCard) { + List expectedTags = expectedPerson.getTags().stream() + .map(tag -> tag.tagName).collect(Collectors.toList()); + assertEquals(expectedTags, actualCard.getTags()); + expectedTags.forEach(tag -> + assertEquals(Arrays.asList(LABEL_DEFAULT_STYLE, getTagColorStyleFor(tag)), + actualCard.getTagStyleClasses(tag))); + } + + + /** + * Asserts that the level of friendship in {@code actualCard} matches all the tags in {@code expectedPerson} with + * the correct symbol. + */ + private static void assertLevelOfFriendshipEqual(Person expectedPerson, + PersonCardHandle actualCard) { + String expectedLevelOfFriendship = expectedPerson.getLevelOfFriendship().value; + int levelOfFriendshipInIntegerForm = Integer.parseInt((expectedLevelOfFriendship)); + String levelOfFriendshipSymbol = ""; + for (int i = 0; i < levelOfFriendshipInIntegerForm; i++) { + levelOfFriendshipSymbol = levelOfFriendshipSymbol + '\u2665' + " "; + } + assertEquals(levelOfFriendshipSymbol, actualCard.getLevelOfFriendship()); + } + +``` +###### \java\seedu\address\ui\testutil\GuiTestAssert.java +``` java + /** + * Changing @param ccaInArrayList into a CCA string in desired format + * @return ccaInString + */ + public static String getCcasInString(List ccaInArrayList) { + String ccaInString = ""; + for (String temp : ccaInArrayList) { + temp = "[" + temp + "] "; + ccaInString = ccaInString + temp; + } + return ccaInString.trim(); + } +} +``` diff --git a/collated/test/fuadsahmawi.md b/collated/test/fuadsahmawi.md new file mode 100644 index 000000000000..b98b6768456f --- /dev/null +++ b/collated/test/fuadsahmawi.md @@ -0,0 +1,635 @@ +# fuadsahmawi +###### \java\seedu\address\logic\commands\AddCommandTest.java +``` java + @Override + public void addReminder(Reminder reminder) throws DuplicateReminderException { + fail("This method should not be called."); + } + + @Override + public void updateFilteredReminderList(Predicate predicate) { + fail("This method should not be called."); + } + + @Override + public ObservableList getFilteredReminderList() { + fail("This method should not be called."); + return null; + } + + @Override + public void deleteReminder(Reminder target) throws ReminderNotFoundException { + fail("This method should not be called."); + } + + } + + /** + * A Model stub that always throw a DuplicatePersonException when trying to add a person. + */ + private class ModelStubThrowingDuplicatePersonException extends ModelStub { + @Override + public void addPerson(Person person) throws DuplicatePersonException { + throw new DuplicatePersonException(); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } + + /** + * A Model stub that always accept the person being added. + */ + private class ModelStubAcceptingPersonAdded extends ModelStub { + final ArrayList personsAdded = new ArrayList<>(); + + @Override + public void addPerson(Person person) throws DuplicatePersonException { + requireNonNull(person); + personsAdded.add(person); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } + +} +``` +###### \java\seedu\address\logic\commands\AddReminderCommandIntegrationTest.java +``` java +/** + * Contains integration tests (interaction with the Model) for {@code AddReminderCommand}. + */ +public class AddReminderCommandIntegrationTest { + + private Model model; + + @Before + public void setUp() { + model = new ModelManager(getTypicalReminderAddressBook(), new UserPrefs()); + } + + @Test + public void execute_newReminder_success() throws Exception { + Reminder validReminder = new ReminderBuilder().withReminderText(VALID_REMINDER_TEXT_B).build(); + + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.addReminder(validReminder); + + assertCommandSuccess(prepareCommand(validReminder, model), model, + String.format(AddReminderCommand.MESSAGE_SUCCESS, validReminder), expectedModel); + } + + @Test + public void execute_duplicateReminder_throwsCommandException() { + Reminder reminderInList = model.getAddressBook().getReminderList().get(0); + assertCommandFailure(prepareCommand(reminderInList, model), + model, AddReminderCommand.MESSAGE_DUPLICATE_REMINDER); + } + + /** + * Generates a new {@code AddRemminderCommand} which upon execution, adds {@code reminder} into the {@code model}. + */ + private AddReminderCommand prepareCommand(Reminder reminder, Model model) { + AddReminderCommand command = new AddReminderCommand(reminder); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } +} +``` +###### \java\seedu\address\logic\commands\AddReminderCommandTest.java +``` java +public class AddReminderCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullReminder_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new AddReminderCommand(null); + } + + /* + @Test + public void execute_reminderAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingReminderAdded modelStub = new ModelStubAcceptingReminderAdded(); + Reminder validReminder = new ReminderBuilder().build(); + + CommandResult commandResult = getAddReminderCommandForReminder(validReminder, modelStub).execute(); + + assertEquals(String.format(AddReminderCommand.MESSAGE_SUCCESS, validReminder), commandResult.feedbackToUser); + assertEquals(Arrays.asList(validReminder), modelStub.remindersAdded); + } + + @Test + public void execute_duplicateReminder_throwsCommandException() throws Exception { + ModelStub modelStub = new ModelStubThrowingDuplicateReminderException(); + Reminder validReminder = new ReminderBuilder().build(); + + thrown.expect(CommandException.class); + thrown.expectMessage(AddReminderCommand.MESSAGE_DUPLICATE_REMINDER); + + getAddReminderCommandForReminder(validReminder, modelStub).execute(); + } + */ + + @Test + public void equals() { + Reminder a = new ReminderBuilder().withReminderText("A").build(); + Reminder b = new ReminderBuilder().withReminderText("B").build(); + AddReminderCommand addReminderACommand = new AddReminderCommand(a); + AddReminderCommand addReminderBCommand = new AddReminderCommand(b); + + // same object -> returns true + assertTrue(addReminderACommand.equals(addReminderACommand)); + + // same values -> returns true + AddReminderCommand addReminderACommandCopy = new AddReminderCommand(a); + assertTrue(addReminderACommand.equals(addReminderACommandCopy)); + + // different types -> returns false + assertFalse(addReminderACommand.equals(1)); + + // null -> returns false + assertFalse(addReminderACommand.equals(null)); + + // different reminder -> returns false + assertFalse(addReminderACommand.equals(addReminderBCommand)); + } + + /** + * Generates a new AddReminderCommand with the details of the given reminder. + */ + private AddReminderCommand getAddReminderCommandForReminder(Reminder reminder, Model model) { + AddReminderCommand command = new AddReminderCommand(reminder); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + /** + * A Model stub that always throw a DuplicateReminderException when trying to add a reminder. + */ + private class ModelStubThrowingDuplicateReminderException extends AddCommandTest.ModelStub { + @Override + public void addReminder(Reminder reminder) throws DuplicateReminderException { + throw new DuplicateReminderException(); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } + + /** + * A Model stub that always accept the reminder being added. + */ + private class ModelStubAcceptingReminderAdded extends AddCommandTest.ModelStub { + final ArrayList remindersAdded = new ArrayList<>(); + + @Override + public void addReminder(Reminder reminder) throws DuplicateReminderException { + requireNonNull(reminder); + remindersAdded.add(reminder); + } + } +} +``` +###### \java\seedu\address\logic\commands\ReminderCommandTestUtil.java +``` java +/** + * Contains helper methods for testing reminder commands. + */ +public class ReminderCommandTestUtil { + public static final String VALID_REMINDER_TEXT_A = "Medical Appointment"; + public static final String VALID_REMINDER_TEXT_B = "CG2271 Finals"; + public static final String VALID_REMINDER_START_DATE_TIME_STRING_A = "2018-03-03 10:30"; + public static final String VALID_REMINDER_START_DATE_TIME_STRING_B = "2018-03-03 10:31"; + public static final String VALID_REMINDER_END_DATE_TIME_STRING_A = "2018-03-03 12:30"; + public static final String VALID_REMINDER_END_DATE_TIME_STRING_B = ""; + public static final String VALID_REMINDER_END_DATE_TIME_STRING_C = "14/10/2018 3pm"; + public static final String VALID_REMINDER_END_DATE_TIME_STRING_D = "10/10/2018 4pm"; + public static final String REMINDER_TEXT_DESC_A = " " + PREFIX_REMINDER_TEXT + VALID_REMINDER_TEXT_A; + public static final String REMINDER_TEXT_DESC_B = " " + PREFIX_REMINDER_TEXT + VALID_REMINDER_TEXT_B; + public static final String REMINDER_START_DATE_TIME_DESC_A = " " + PREFIX_DATE + + VALID_REMINDER_START_DATE_TIME_STRING_A; + public static final String REMINDER_START_DATE_TIME_DESC_B = " " + PREFIX_DATE + + VALID_REMINDER_START_DATE_TIME_STRING_B; + public static final String REMINDER_END_DATE_TIME_DESC_A = " " + PREFIX_END_DATE + + VALID_REMINDER_END_DATE_TIME_STRING_A; + public static final String REMINDER_END_DATE_TIME_DESC_B = " " + PREFIX_END_DATE + + VALID_REMINDER_END_DATE_TIME_STRING_B; + + public static final String INVALID_REMINDER_TEXT_DESC = " " + PREFIX_REMINDER_TEXT + ""; + public static final String PREAMBLE_WHITESPACE = "\t \r \n"; + public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble"; + + public static final LocalDateTime VALID_REMINDER_START_DATE_TIME_A; + public static final LocalDateTime VALID_REMINDER_START_DATE_TIME_B; + + + static { + VALID_REMINDER_START_DATE_TIME_A = getLocalDateTimeFromString(VALID_REMINDER_START_DATE_TIME_STRING_A); + VALID_REMINDER_START_DATE_TIME_B = getLocalDateTimeFromString(VALID_REMINDER_START_DATE_TIME_STRING_B); + } + + /** + * Executes the given {@code command}, confirms that
+ * - the result message matches {@code expectedMessage}
+ * - the {@code actualModel} matches {@code expectedModel} + */ + public static void assertCommandSuccess(Command command, Model actualModel, String expectedMessage, + Model expectedModel) { + try { + CommandResult result = command.execute(); + assertEquals(expectedMessage, result.feedbackToUser); + assertEquals(expectedModel, actualModel); + } catch (CommandException ce) { + throw new AssertionError("Execution of command should not fail.", ce); + } + } + + /** + * Executes the given {@code command}, confirms that
+ * - a {@code CommandException} is thrown
+ * - the CommandException message matches {@code expectedMessage}
+ * - the address book and the filtered REMINDER list in the {@code actualModel} remain unchanged + */ + public static void assertCommandFailure(Command command, Model actualModel, String expectedMessage) { + // we are unable to defensively copy the model for comparison later, so we can + // only do so by copying its components. + AddressBook expectedAddressBook = new AddressBook(actualModel.getAddressBook()); + List expectedFilteredList = new ArrayList<>(actualModel.getFilteredReminderList()); + + try { + command.execute(); + fail("The expected CommandException was not thrown."); + } catch (CommandException e) { + assertEquals(expectedMessage, e.getMessage()); + assertEquals(expectedAddressBook, actualModel.getAddressBook()); + assertEquals(expectedFilteredList, actualModel.getFilteredReminderList()); + } + } + + /** + * Deletes the first REMINDER in {@code model}'s list from {@code model}'s address book. + */ + public static void deleteFirstReminder(Model model) { + Reminder firstReminder = model.getFilteredReminderList().get(0); + try { + model.deleteReminder(firstReminder); + } catch (ReminderNotFoundException pnfe) { + throw new AssertionError("Reminder in filtered list must exist in model.", pnfe); + } + } +} +``` +###### \java\seedu\address\logic\parser\AddReminderCommandParserTest.java +``` java +public class AddReminderCommandParserTest { + private AddReminderCommandParser parser = new AddReminderCommandParser(); + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddReminderCommand.MESSAGE_USAGE); + + // missing end date time prefix + assertParseFailure(parser, REMINDER_TEXT_DESC_B + REMINDER_START_DATE_TIME_DESC_B, expectedMessage); + + // missing reminder text prefix + assertParseFailure(parser, REMINDER_START_DATE_TIME_DESC_B + REMINDER_END_DATE_TIME_DESC_B, + expectedMessage); + + // missing start date time prefix + assertParseFailure(parser, REMINDER_TEXT_DESC_B + REMINDER_END_DATE_TIME_DESC_B, + expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + // invalid goal text + assertParseFailure(parser, INVALID_REMINDER_TEXT_DESC + REMINDER_START_DATE_TIME_DESC_A + + REMINDER_END_DATE_TIME_DESC_A, + String.format(MESSAGE_INVALID_DATE_FORMAT, AddReminderCommand.MESSAGE_USAGE)); + + // non-empty preamble + assertParseFailure(parser, PREAMBLE_NON_EMPTY + REMINDER_TEXT_DESC_B + REMINDER_START_DATE_TIME_DESC_B + + REMINDER_END_DATE_TIME_DESC_B, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddReminderCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\model\reminder\DateTimeTest.java +``` java +public class DateTimeTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new DateTime(null)); + } + + @Test + public void isValidGoalText() { + // null reminder text + Assert.assertThrows(NullPointerException.class, () -> ReminderText.isValidReminderText(null)); + + // blank reminder text + assertFalse(DateTime.isValidDateTime("")); // empty string + assertFalse(DateTime.isValidDateTime(" ")); // spaces only + assertFalse(DateTime.isValidDateTime("aaa0")); // alphanumerical + assertFalse(DateTime.isValidDateTime("!@$#()_+")); // symbols only + + // valid reminder text + assertTrue(DateTime.isValidDateTime("1")); + assertTrue(DateTime.isValidDateTime("-1.122ewk:!@|!+@!*~")); // all kinds of symbols and alphanumerical + assertTrue(DateTime.isValidDateTime("! 1 wq ")); // with spaces + assertTrue(DateTime.isValidDateTime(" 7")); // spaces with a value + } +} +``` +###### \java\seedu\address\model\reminder\ReminderTextPredicateTest.java +``` java +public class ReminderTextPredicateTest { + + @Test + public void equals() { + + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + ReminderTextPredicate firstPredicate = new ReminderTextPredicate(firstPredicateKeywordList); + ReminderTextPredicate secondPredicate = new ReminderTextPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + ReminderTextPredicate firstPredicateCopy = new ReminderTextPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different reminder -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_reminderTextContainsKeywords_returnsTrue() { + // One keyword + ReminderTextPredicate predicate = new ReminderTextPredicate(Collections.singletonList("gym")); + assertTrue(predicate.test(new ReminderBuilder().withReminderText("Go gym").build())); + + // Multiple keywords + predicate = new ReminderTextPredicate(Arrays.asList("Go", "gym")); + assertTrue(predicate.test(new ReminderBuilder().withReminderText("Go gym").build())); + + // Only one matching keyword + predicate = new ReminderTextPredicate(Arrays.asList("Star", "Gym")); + assertTrue(predicate.test(new ReminderBuilder().withReminderText("Start Gym").build())); + + // Mixed-case keywords + predicate = new ReminderTextPredicate(Arrays.asList("gO", "gYM")); + assertTrue(predicate.test(new ReminderBuilder().withReminderText("Go gym").build())); + } + + @Test + public void test_reminderTextDoesNotContainKeywords_returnsFalse() { + // Zero keywords + ReminderTextPredicate predicate = new ReminderTextPredicate(Collections.emptyList()); + assertFalse(predicate.test(new ReminderBuilder().withReminderText("Gym").build())); + + // Non-matching keyword + predicate = new ReminderTextPredicate(Arrays.asList("Finals")); + assertFalse(predicate.test(new ReminderBuilder().withReminderText("Midterms").build())); + + // Keywords match start date time and end date time, but does not match reminder text + predicate = new ReminderTextPredicate(Arrays.asList("tmr", "8pm", "tmr", "10pm")); + assertFalse(predicate.test(new ReminderBuilder().withReminderText("Alice").withDateTime("tmr 8pm") + .withEndDateTime("tmr 10pm").build())); + } +} +``` +###### \java\seedu\address\model\reminder\ReminderTextTest.java +``` java +public class ReminderTextTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new ReminderText(null)); + } + + @Test + public void constructor_invalidReminderText_throwsIllegalArgumentException() { + String invalidReminderText = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new ReminderText(invalidReminderText)); + } + + @Test + public void isValidGoalText() { + // null reminder text + Assert.assertThrows(NullPointerException.class, () -> ReminderText.isValidReminderText(null)); + + // blank reminder text + assertFalse(ReminderText.isValidReminderText("")); // empty string + assertFalse(ReminderText.isValidReminderText(" ")); // spaces only + + // valid reminder text + assertTrue(ReminderText.isValidReminderText("1")); + assertTrue(ReminderText.isValidReminderText("aaa0")); // alphanumerical + assertTrue(ReminderText.isValidReminderText("!@$#()_+")); // symbols only + assertTrue(ReminderText.isValidReminderText("-1.122ewk:!@|!+@!*~")); // all kinds of symbols and alphanumerical + assertTrue(ReminderText.isValidReminderText("! 1 wq ")); // with spaces + assertTrue(ReminderText.isValidReminderText(" 7")); // spaces with a value + } +} +``` +###### \java\seedu\address\storage\XmlAdaptedReminderTest.java +``` java +public class XmlAdaptedReminderTest { + + private static final String INVALID_REMINDER_TEXT = " "; + + private static final String VALID_REMINDER_TEXT = REMINDER_B.getReminderText().toString(); + private static final String VALID_START_DATE_TIME = REMINDER_B.getDateTime().toString(); + private static final String VALID_END_DATE_TIME = REMINDER_B.getEndDateTime().toString(); + + @Test + public void toModelType_invalidReminderText_throwsIllegalValueException() { + XmlAdaptedReminder reminder = + new XmlAdaptedReminder(INVALID_REMINDER_TEXT, VALID_START_DATE_TIME, VALID_END_DATE_TIME); + String expectedMessage = ReminderText.MESSAGE_REMINDER_TEXT_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, reminder::toModelType); + } + + @Test + public void toModelType_nullReminderText_throwsIllegalValueException() { + XmlAdaptedReminder reminder = new XmlAdaptedReminder(null, VALID_START_DATE_TIME, + VALID_END_DATE_TIME); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, ReminderText.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, reminder::toModelType); + } + + + @Test + public void toModelType_nullStartDateTime_throwsIllegalValueException() { + XmlAdaptedReminder reminder = new XmlAdaptedReminder(VALID_REMINDER_TEXT, null, VALID_END_DATE_TIME); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, DateTime.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, reminder::toModelType); + } + + @Test + public void toModelType_nullEndDateTime_throwsIllegalValueException() { + XmlAdaptedReminder reminder = new XmlAdaptedReminder(VALID_REMINDER_TEXT, VALID_START_DATE_TIME, + null); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, EndDateTime.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, reminder::toModelType); + } +} +``` +###### \java\seedu\address\testutil\ReminderBuilder.java +``` java +/** + * A utility class to help with building Reminder objects. + */ +public class ReminderBuilder { + + public static final String DEFAULT_END_DATE_TIME = "2017-04-08 13:30"; + public static final String DEFAULT_REMINDER_TEXT = "go home play game"; + public static final String DEFAULT_START_DATE_TIME = "2017-04-08 12:30"; + + private EndDateTime endDateTime; + private ReminderText reminderText; + private DateTime dateTime; + + public ReminderBuilder() { + endDateTime = new EndDateTime(DEFAULT_END_DATE_TIME); + reminderText = new ReminderText(DEFAULT_REMINDER_TEXT); + dateTime = new DateTime(DEFAULT_START_DATE_TIME); + } + + /** + * Initializes the GoalBuilder with the data of {@code goalToCopy}. + */ + public ReminderBuilder(Reminder reminderToCopy) { + endDateTime = reminderToCopy.getEndDateTime(); + reminderText = reminderToCopy.getReminderText(); + dateTime = reminderToCopy.getDateTime(); + } + + /** + * Sets the {@code EndDateTime} of the {@code Reminder} that we are building. + */ + public ReminderBuilder withEndDateTime(String endDateTime) { + this.endDateTime = new EndDateTime(endDateTime); + return this; + } + + /** + * Sets the {@code ReminderText} of the {@code Reminder} that we are building. + */ + public ReminderBuilder withReminderText(String reminderText) { + this.reminderText = new ReminderText(reminderText); + return this; + } + + /** + * Sets the {@code StartDateTime} of the {@code Goal} that we are building. + */ + public ReminderBuilder withDateTime(String startDateTime) { + this.dateTime = new DateTime(startDateTime); + return this; + } + + public Reminder build() { + return new Reminder(reminderText, dateTime, endDateTime); + } +} +``` +###### \java\seedu\address\testutil\ReminderUtil.java +``` java +/** + * A utility class for Reminder. + */ +public class ReminderUtil { + + /** + * Returns an addreminder command string for adding the {@code reminder}. + */ + public static String getAddReminderCommand(Reminder reminder) { + return AddReminderCommand.COMMAND_WORD + " " + getReminderDetails(reminder); + } + + /** + * Returns the part of command string for the given {@code goal}'s details. + */ + public static String getReminderDetails(Reminder reminder) { + StringBuilder sb = new StringBuilder(); + sb.append(PREFIX_REMINDER_TEXT + reminder.getReminderText().toString() + " "); + sb.append(PREFIX_DATE + reminder.getDateTime().toString() + " "); + sb.append(PREFIX_END_DATE + reminder.getEndDateTime().toString() + " "); + return sb.toString(); + } +} +``` +###### \java\seedu\address\testutil\TypicalReminders.java +``` java +/** + * A utility class containing a list of {@code Reminder} objects to be used in tests. + */ +public class TypicalReminders { + public static final Reminder REMINDER_A = new ReminderBuilder() + .withDateTime("2017-04-08 12:30") + .withEndDateTime("2017-04-08 14:30") + .withReminderText("go to the gym").build(); + public static final Reminder REMINDER_B = new ReminderBuilder() + .withDateTime("2017-06-08 15:30") + .withEndDateTime("2017-06-08 17:30") + .withReminderText("medical appointment").build(); + public static final Reminder REMINDER_C = new ReminderBuilder() + .withDateTime("2017-05-10 12:30") + .withEndDateTime("2017-05-10 14:30") + .withReminderText("cc").build(); + public static final Reminder REMINDER_D = new ReminderBuilder() + .withDateTime("2018-04-08 12:30") + .withEndDateTime("2018-04-08 14:30") + .withReminderText("dd").build(); + public static final Reminder REMINDER_E = new ReminderBuilder() + .withDateTime("2017-07-07 13:30") + .withEndDateTime("2017-07-07 14:30") + .withReminderText("ee").build(); + public static final Reminder REMINDER_F = new ReminderBuilder() + .withDateTime("2017-03-10 09:30") + .withEndDateTime("2017-03-10 10:30") + .withReminderText("ff").build(); + + private TypicalReminders() {} // prevents instantiation + + /** + * Returns an {@code AddressBook} with all the typical reminders. + */ + public static AddressBook getTypicalReminderAddressBook() { + AddressBook ab = new AddressBook(); + for (Reminder reminder : getTypicalReminders()) { + try { + ab.addReminder(reminder); + } catch (DuplicateReminderException e) { + throw new AssertionError("not possible"); + } + } + return ab; + } + + public static List getTypicalReminders() { + return new ArrayList<>(Arrays.asList(REMINDER_A, REMINDER_B, REMINDER_C, REMINDER_D, REMINDER_E, REMINDER_F)); + } +} +``` diff --git a/collated/test/sham-sheer.md b/collated/test/sham-sheer.md new file mode 100644 index 000000000000..99b927c4aff2 --- /dev/null +++ b/collated/test/sham-sheer.md @@ -0,0 +1,647 @@ +# sham-sheer +###### \java\guitests\guihandles\CommandBoxHandle.java +``` java + /** + * Enters the given command in the Command Box but doesnt press enter. + * @return true if the command succeeded, false otherwise. + */ + public boolean runWithoutEnter(String command) { + click(); + guiRobot.interact(() -> getRootNode().setText(command)); + guiRobot.pauseForHuman(); + + return !getStyleClass().contains(CommandBox.ERROR_STYLE_CLASS); + } + + /** + * Returns the list of style classes present in the command box. + */ + public ObservableList getStyleClass() { + return getRootNode().getStyleClass(); + } +} +``` +###### \java\seedu\address\logic\commands\AddCommandTest.java +``` java + @Override + public void sortPersons(Index index) throws IndexOutOfBoundsException { + fail("This method should not be called."); + } + + public void deleteMeetDate(Person person) throws PersonNotFoundException { + fail("This method should not be called."); + } + +``` +###### \java\seedu\address\logic\commands\DeleteMeetCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for + * {@code DeleteCommand}. + */ +public class DeleteMeetCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() throws Exception { + Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + DeleteMeetCommand deleteMeetCommand = prepareCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(DeleteMeetCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteMeetDate(personToDelete); + + assertCommandSuccess(deleteMeetCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() throws Exception { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + DeleteMeetCommand deleteMeetCommand = prepareCommand(outOfBoundIndex); + + assertCommandFailure(deleteMeetCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + /*@Test + public void execute_validIndexFilteredList_success() throws Exception { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + DeleteMeetCommand deleteMeetCommand = prepareCommand(INDEX_FIRST_PERSON); + + String expectedMessage = String.format(DeleteMeetCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); + + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteMeetDate(personToDelete); + showNoPerson(expectedModel); + + assertCommandSuccess(deleteMeetCommand, model, expectedMessage, expectedModel); + }*/ + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Index outOfBoundIndex = INDEX_SECOND_PERSON; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); + + DeleteMeetCommand deleteMeetCommand = prepareCommand(outOfBoundIndex); + + assertCommandFailure(deleteMeetCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void executeUndoRedo_validIndexUnfilteredList_success() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + DeleteMeetCommand deleteMeetCommand = prepareCommand(INDEX_FIRST_PERSON); + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + // delete -> first person deleted + deleteMeetCommand.execute(); + undoRedoStack.push(deleteMeetCommand); + + // undo -> reverts addressbook back to previous state and filtered person list to show all persons + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + // redo -> same first person deleted again + expectedModel.deleteMeetDate(personToDelete); + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void executeUndoRedo_invalidIndexUnfilteredList_failure() { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + DeleteMeetCommand deleteMeetCommand = prepareCommand(outOfBoundIndex); + + // execution failed -> deleteCommand not pushed into undoRedoStack + assertCommandFailure(deleteMeetCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + + // no commands in undoRedoStack -> undoCommand and redoCommand fail + assertCommandFailure(undoCommand, model, UndoCommand.MESSAGE_FAILURE); + assertCommandFailure(redoCommand, model, RedoCommand.MESSAGE_FAILURE); + } + + /** + * 1. Deletes a {@code Person} from a filtered list. + * 2. Undo the deletion. + * 3. The unfiltered list should be shown now. Verify that the index of the previously deleted person in the + * unfiltered list is different from the index at the filtered list. + * 4. Redo the deletion. This ensures {@code RedoCommand} deletes the person object regardless of indexing. + */ + @Test + public void executeUndoRedo_validIndexFilteredList_samePersonDeleted() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + DeleteMeetCommand deleteMeetCommand = prepareCommand(INDEX_FIRST_PERSON); + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + showPersonAtIndex(model, INDEX_SECOND_PERSON); + Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + // delete -> deletes second person in unfiltered person list / first person in filtered person list + deleteMeetCommand.execute(); + undoRedoStack.push(deleteMeetCommand); + + // undo -> reverts addressbook back to previous state and filtered person list to show all persons + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + expectedModel.deleteMeetDate(personToDelete); + assertNotEquals(personToDelete, model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased())); + // redo -> deletes same second person in unfiltered person list + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void equals() throws Exception { + DeleteMeetCommand deleteFirstMeetCommand = prepareCommand(INDEX_FIRST_PERSON); + DeleteMeetCommand deleteSecondMeetCommand = prepareCommand(INDEX_SECOND_PERSON); + + // same object -> returns true + assertTrue(deleteFirstMeetCommand.equals(deleteFirstMeetCommand)); + + // same values -> returns true + DeleteMeetCommand deleteFirstMeetCommandCopy = prepareCommand(INDEX_FIRST_PERSON); + assertTrue(deleteFirstMeetCommand.equals(deleteFirstMeetCommandCopy)); + + // one command preprocessed when previously equal -> returns false + deleteFirstMeetCommandCopy.preprocessUndoableCommand(); + assertFalse(deleteFirstMeetCommand.equals(deleteFirstMeetCommandCopy)); + + // different types -> returns false + assertFalse(deleteFirstMeetCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFirstMeetCommand.equals(null)); + + // different person -> returns false + assertFalse(deleteFirstMeetCommand.equals(deleteSecondMeetCommand)); + } + + /** + * Returns a {@code DeleteMeetCommand} with the parameter {@code index}. + */ + private DeleteMeetCommand prepareCommand(Index index) { + DeleteMeetCommand deleteMeetCommand = new DeleteMeetCommand(index); + deleteMeetCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return deleteMeetCommand; + } + + /** + * Updates {@code model}'s filtered list to show no one. + */ + private void showNoPerson(Model model) { + model.updateFilteredPersonList(p -> false); + + assertTrue(model.getFilteredPersonList().isEmpty()); + } +} +``` +###### \java\seedu\address\logic\commands\MeetCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) and unit tests for SortCommand. + */ +public class MeetCommandTest { + + public static final String MEETDATE_STUB = "14/04/2018"; + + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + + @Test + public void execute_addMeetDateUnfilteredList_success() throws Exception { + Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person editedPerson = new PersonBuilder(firstPerson).withMeetDate(MEETDATE_STUB).build(); + + MeetCommand meetCommand = prepareCommand(INDEX_FIRST_PERSON, editedPerson.getMeetDate().value); + + String expectedMessage = String.format(MeetCommand.MESSAGE_ADD_MEETDATE_SUCCESS, editedPerson.getName()); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + expectedModel.updatePerson(firstPerson, editedPerson); + + assertCommandSuccess(meetCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_deleteMeetDateUnfilteredList_success() throws Exception { + Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person editedPerson = new PersonBuilder(firstPerson).withMeetDate("").build(); + + MeetCommand meetCommand = prepareCommand(INDEX_FIRST_PERSON, editedPerson.getMeetDate().toString()); + + String expectedMessage = String.format(MeetCommand.MESSAGE_DELETE_MEETDATE_SUCCESS, editedPerson.getName()); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + expectedModel.updatePerson(firstPerson, editedPerson); + + assertCommandSuccess(meetCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_filteredList_success() throws Exception { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + + Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person editedPerson = new PersonBuilder(model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased())) + .withMeetDate(MEETDATE_STUB).build(); + + MeetCommand meetCommand = prepareCommand(INDEX_FIRST_PERSON, editedPerson.getMeetDate().value); + + String expectedMessage = String.format(MeetCommand.MESSAGE_ADD_MEETDATE_SUCCESS, editedPerson.getName()); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + expectedModel.updatePerson(firstPerson, editedPerson); + + assertCommandSuccess(meetCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidPersonIndexUnfilteredList_failure() throws Exception { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + MeetCommand meetCommand = prepareCommand(outOfBoundIndex, VALID_MEETDATE_BOB); + + assertCommandFailure(meetCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + /** + * Edit filtered list where index is larger than size of filtered list, + * but smaller than size of address book + */ + @Test + public void execute_invalidPersonIndexFilteredList_failure() throws Exception { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + Index outOfBoundIndex = INDEX_SECOND_PERSON; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); + + MeetCommand meetCommand = prepareCommand(outOfBoundIndex, VALID_MEETDATE_BOB); + + assertCommandFailure(meetCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void executeUndoRedo_validIndexUnfilteredList_success() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Person personToModify = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person modifiedPerson = new PersonBuilder(personToModify).withMeetDate(MEETDATE_STUB).build(); + MeetCommand meetCommand = prepareCommand(INDEX_FIRST_PERSON, MEETDATE_STUB); + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + // meet date -> first person meet date changed + meetCommand.execute(); + undoRedoStack.push(meetCommand); + + // undo -> reverts addressbook back to previous state and filtered person list to show all persons + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + // redo -> same first person modified again + expectedModel.updatePerson(personToModify, modifiedPerson); + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void executeUndoRedo_invalidIndexUnfilteredList_failure() { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + MeetCommand meetCommand = prepareCommand(outOfBoundIndex, ""); + + // execution failed -> remarkCommand not pushed into undoRedoStack + assertCommandFailure(meetCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + + // no commands in undoRedoStack -> undoCommand and redoCommand fail + assertCommandFailure(undoCommand, model, UndoCommand.MESSAGE_FAILURE); + assertCommandFailure(redoCommand, model, RedoCommand.MESSAGE_FAILURE); + } + + /** + * 1. Modifies {@code Person#remark} from a filtered list. + * 2. Undo the modification. + * 3. The unfiltered list should be shown now. Verify that the index of the previously modified person in the + * unfiltered list is different from the index at the filtered list. + * 4. Redo the modification. This ensures {@code RedoCommand} modifies the person object regardless of indexing. + */ + @Test + public void executeUndoRedo_validIndexFilteredList_samePersonDeleted() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + MeetCommand remarkCommand = prepareCommand(INDEX_FIRST_PERSON, MEETDATE_STUB); + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + showPersonAtIndex(model, INDEX_SECOND_PERSON); + Person personToModify = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person modifiedPerson = new PersonBuilder(personToModify).withMeetDate(MEETDATE_STUB).build(); + // meet date -> modifies second person in unfiltered person list / first person in filtered person list + remarkCommand.execute(); + undoRedoStack.push(remarkCommand); + + // undo -> reverts addressbook back to previous state and filtered person list to show all persons + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + expectedModel.updatePerson(personToModify, modifiedPerson); + assertNotEquals(personToModify, model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased())); + // redo -> modifies same second person in unfiltered person list + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + } + + + + @Test + public void equals() { + String testDate = "15/03/2018"; + String testDateTwo = "16/03/2018"; + final MeetCommand standardCommand = new MeetCommand(INDEX_FIRST_PERSON, new Meet(testDate)); + + // same values -> returns true + MeetCommand commandWithSameValues = new MeetCommand(INDEX_FIRST_PERSON, new Meet(testDate)); + assertTrue(standardCommand.equals(commandWithSameValues)); + + // same object -> returns true + assertTrue(standardCommand.equals(standardCommand)); + + // null -> returns false + assertFalse(standardCommand.equals(null)); + + // different types -> returns false + assertFalse(standardCommand.equals(new ClearCommand())); + + // different index -> returns false + assertFalse(standardCommand.equals(new MeetCommand(INDEX_SECOND_PERSON, new Meet(testDate)))); + + // different remark -> returns false + assertFalse(standardCommand.equals(new MeetCommand(INDEX_FIRST_PERSON, new Meet(testDateTwo)))); + } + + /** + * Returns an {@code RemarkCommand} with parameters {@code index} and {@code remark}. + */ + private MeetCommand prepareCommand(Index index, String date) { + MeetCommand meetCommand = new MeetCommand(index, new Meet(date)); + meetCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return meetCommand; + } +} + +``` +###### \java\seedu\address\logic\commands\SortCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) and unit tests for SortCommand. + */ +public class SortCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + + + @Test + public void execute_validIndexUnfilteredList_success() throws Exception { + Index sortType = INDEX_SORT_LEVEL_OF_FRIENDSHIP; + SortCommand sortCommand = prepareCommand(sortType); + + String expectedMessage = String.format(SortCommand.MESSAGE_SORTED_SUCCESS_LEVEL_OF_FRIENDSHIP, sortType); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.sortPersons(sortType); + + assertCommandSuccess(sortCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_validIndexUnfilteredList2_success() throws Exception { + Index sortType = INDEX_SORT_MEET_DATE; + SortCommand sortCommand = prepareCommand(sortType); + + String expectedMessage = String.format(SortCommand.MESSAGE_SORTED_SUCCESS_MEET_DATE, sortType); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.sortPersons(sortType); + + assertCommandSuccess(sortCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_validIndexUnfilteredList3_success() throws Exception { + Index sortType = INDEX_SORT_BIRTHDAY; + SortCommand sortCommand = prepareCommand(sortType); + + String expectedMessage = String.format(SortCommand.MESSAGE_SORTED_SUCCESS_BIRTHDAY, sortType); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.sortPersons(sortType); + + assertCommandSuccess(sortCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() throws Exception { + Index outOfBoundIndex = Index.fromOneBased(4); + SortCommand sortCommand = prepareCommand(outOfBoundIndex); + + assertCommandFailure(sortCommand, model, String.format(SortCommand.MESSAGE_INVALID_COMMAND_FORMAT, 4)); + } + + @Test + public void equals() throws Exception { + SortCommand sortFirstCommand = prepareCommand(Index.fromOneBased(1)); + SortCommand sortSecondCommand = prepareCommand(Index.fromOneBased(3)); + + // same object -> returns true + assertTrue(sortFirstCommand.equals(sortFirstCommand)); + + // same values -> returns true + SortCommand sortFirstCommandCopy = prepareCommand(Index.fromOneBased(1)); + assertTrue(sortFirstCommand.equals(sortFirstCommandCopy)); + + // one command preprocessed when previously equal -> returns false + sortFirstCommandCopy.preprocessUndoableCommand(); + assertTrue(sortFirstCommand.equals(sortFirstCommandCopy)); + + // different types -> returns false + assertFalse(sortFirstCommand.equals(1)); + + // null -> returns false + assertFalse(sortFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(sortFirstCommand.equals(sortSecondCommand)); + } + + private SortCommand prepareCommand(Index index) { + SortCommand sortCommand = new SortCommand(index); + sortCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return sortCommand; + } +} +``` +###### \java\seedu\address\logic\parser\DeleteMeetCommandParserTest.java +``` java +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the DeleteCommand code. For example, inputs "1" and "1 abc" take the + * same path through the DeleteCommand, and therefore we test only one of them. + * The path variation for those two cases occur inside the ParserUtil, and + * therefore should be covered by the ParserUtilTest. + */ +public class DeleteMeetCommandParserTest { + + private DeleteMeetCommandParser parser = new DeleteMeetCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteMeetCommand() { + assertParseSuccess(parser, "1", new DeleteMeetCommand(INDEX_FIRST_PERSON)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteMeetCommand.MESSAGE_USAGE)); + } + +} +``` +###### \java\seedu\address\logic\parser\MeetCommandParserTest.java +``` java +public class MeetCommandParserTest { + private MeetCommandParser parser = new MeetCommandParser(); + private final String nonEmptyDate = "15/03/2018"; + + @Test + public void parse_indexSpecified_success() throws Exception { + //have a date + Index targetIndex = INDEX_FIRST_PERSON; + String userInput = targetIndex.getOneBased() + " " + PREFIX_DATE.toString() + nonEmptyDate; + MeetCommand expectedCommand = new MeetCommand(INDEX_FIRST_PERSON, new Meet(nonEmptyDate)); + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_missingCompulsoryField_failure() throws Exception { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, MeetCommand.MESSAGE_USAGE); + + // no parameters + assertParseFailure(parser, MeetCommand.COMMAND_WORD, expectedMessage); + + // no index + assertParseFailure(parser, MeetCommand.COMMAND_WORD + " " + nonEmptyDate, expectedMessage); + } + +} +``` +###### \java\seedu\address\logic\parser\SortCommandParserTest.java +``` java +public class SortCommandParserTest { + private SortCommandParser parser = new SortCommandParser(); + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\model\person\MeetTest.java +``` java +public class MeetTest { + + @Test + public void equals() { + Meet meet = new Meet("14/01/2018"); + + // same object -> return true + assertTrue(meet.equals(meet)); + + // same values -> returns true + Meet meetDuplicate = new Meet(meet.value); + assertTrue(meet.equals(meetDuplicate)); + + // different types -> returns false + assertFalse(meet.equals("14/01/2018")); + + // null -> returns false + assertFalse(meet.equals(null)); + + // different meet -> returns false; + Meet differentMeet = new Meet("15/01/2018"); + assertFalse(meet.equals(differentMeet)); + } + + + @Test + public void isValidDate() { + // null meet date + Assert.assertThrows(NullPointerException.class, () -> Meet.isValidDate(null)); + + // blank meet date + assertFalse(Meet.isValidDate("")); // empty string + assertFalse(Meet.isValidDate(" ")); // spaces only + + // missing parts + assertFalse(Meet.isValidDate("12--1997")); // missing month part + assertFalse(Meet.isValidDate("--12-1998")); // missing date part + assertFalse(Meet.isValidDate("//12/1998")); // missing date part + assertFalse(Meet.isValidDate("12-12-")); // missing year part + assertFalse(Meet.isValidDate("12/12/")); // missing year part + + + // invalid parts + assertFalse(Meet.isValidDate("32-Jan-2000")); // invalid day + assertFalse(Meet.isValidDate("33.01.2000")); // invalid day + assertFalse(Meet.isValidDate("20/20/2000")); // invalid month + assertFalse(Meet.isValidDate("20/13/1997")); // invalid month + assertFalse(Meet.isValidDate("29/Feb/2001")); // invalid due to leap year + assertFalse(Meet.isValidDate("31/04/2000")); // invalid day for month of April + assertFalse(Meet.isValidDate("31/Sep/2000")); // invalid day for month of September + assertFalse(Meet.isValidDate("31//01/2000")); // invalid meet date format + assertFalse(Meet.isValidDate("31..01..2000")); // invalid meet date format + assertFalse(Meet.isValidDate("20--2-1997")); // invalid meet date format + assertFalse(Meet.isValidDate("20*2*1997")); // invalid symbols + assertFalse(Meet.isValidDate("12 / 12 / 2012")); // contains spaces + assertFalse(Meet.isValidDate("01/Jan/2000")); // using / + assertFalse(Meet.isValidDate("31.Jan.2000")); // using . + assertFalse(Meet.isValidDate("01-12-2000")); // using - + assertFalse(Meet.isValidDate("28-Feb-2001")); + + // valid meet date + assertTrue(Meet.isValidDate("01/01/2000")); + } +} +``` +###### \java\seedu\address\testutil\PersonBuilder.java +``` java + /** + * Sets the {@code Email} of the {@code Person} that we are building. + */ + public PersonBuilder withMeetDate(String meetDate) { + this.meetDate = new Meet(meetDate); + return this; + } + + public Person build() { + return new Person(name, phone, birthday, levelOfFriendship, unitNumber, ccas, meetDate, tags); + } + +} +``` +###### \java\seedu\address\ui\CommandBoxTest.java +``` java + @Test + public void handleKeyPress_startingWithTab() { + commandBoxHandle.runWithoutEnter(COMMAND_INCOMPLETE); + assertInputHistory(KeyCode.TAB, COMMAND_COMPLETE); + } + +``` diff --git a/collated/test/zuweitrack.md b/collated/test/zuweitrack.md new file mode 100644 index 000000000000..d41184b62f05 --- /dev/null +++ b/collated/test/zuweitrack.md @@ -0,0 +1,329 @@ +# zuweitrack +###### \java\seedu\address\logic\commands\SeekRaCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) for {@code SeekRaCommand}. + */ +public class SeekRaCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void equals() { + UnitNumberContainsKeywordsPredicate firstPredicate = + new UnitNumberContainsKeywordsPredicate(Collections.singletonList("first")); + UnitNumberContainsKeywordsPredicate secondPredicate = + new UnitNumberContainsKeywordsPredicate(Collections.singletonList("second")); + + SeekRaCommand seekRaFirstCommand = new SeekRaCommand(firstPredicate); + SeekRaCommand seekRaSecondCommand = new SeekRaCommand(secondPredicate); + + // same object -> returns true + assertTrue(seekRaFirstCommand.equals(seekRaFirstCommand)); + + // same values -> returns true + SeekRaCommand seekRaFirstCommandCopy = new SeekRaCommand(firstPredicate); + assertTrue(seekRaFirstCommand.equals(seekRaFirstCommandCopy)); + + // different types -> returns false + assertFalse(seekRaFirstCommand.equals(1)); + + // null -> returns false + assertFalse(seekRaFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(seekRaFirstCommand.equals(seekRaSecondCommand)); + } + + @Test + public void execute_zeroKeywords_noPersonFound() { + String expectedMessage = String.format(MESSAGE_RA_LISTED_OVERVIEW, 0); + SeekRaCommand command = prepareNameCommand(" "); + assertCommandSuccess(command, expectedMessage, Collections.emptyList()); + } + + @Test + public void execute_multipleNameKeywords_multiplePersonsFound() { + String expectedMessage = String.format(MESSAGE_RA_LISTED_OVERVIEW, 3); + SeekRaCommand command = prepareNameCommand("Kurz Elle Kunz"); + assertCommandSuccess(command, expectedMessage, Arrays.asList(CARL, ELLE, FIONA)); + } + + @Test + public void execute_multipleTagKeywords_multiplePersonsFound() { + String expectedMessage = String.format(MESSAGE_RA_LISTED_OVERVIEW, 7); + SeekRaCommand command = prepareTagCommand("friends owesMoney"); + assertCommandSuccess(command, expectedMessage, Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE)); + } + + /** + * Parses {@code userInput} into a {@code SeekRaCommand}. + */ + private SeekRaCommand prepareNameCommand(String userInput) { + SeekRaCommand command = + new SeekRaCommand(new UnitNumberContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+")))); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + /** + * Parses {@code userInput} into a {@code SeekRaCommand}. + */ + private SeekRaCommand prepareTagCommand(String userInput) { + SeekRaCommand command = + new SeekRaCommand(new UnitNumberContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+")))); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + /** + * Asserts that {@code command} is successfully executed, and
+ * - the command feedback is equal to {@code expectedMessage}
+ * - the {@code FilteredList} is equal to {@code expectedList}
+ * - the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccess(SeekRaCommand command, String expectedMessage, List expectedList) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + CommandResult commandResult = command.execute(); + + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedList, model.getFilteredPersonList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } +} +``` +###### \java\seedu\address\logic\commands\ShowLofCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) for {@code ShowLofCommand}. + */ +public class ShowLofCommandTest { + private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + + @Test + public void execute_zeroValues_noPersonFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); + ShowLofCommand command = prepareLofCommand(" "); + assertCommandSuccess(command, expectedMessage, Collections.emptyList()); + } + + @Test + public void execute_multipleKeyValues_multiplePersonsFound() { + String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3); + ShowLofCommand command = prepareLofCommand("7 10 3"); + assertCommandSuccess(command, expectedMessage, Arrays.asList(CARL, ELLE, FIONA)); + } + + + /** + * Parses {@code userInput} into a {@code ShowLofCommand}. + */ + private ShowLofCommand prepareLofCommand(String userInput) { + ShowLofCommand command = + new ShowLofCommand(new LofContainsValuePredicate(Arrays.asList(userInput.split("\\s+")))); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + /** + * Asserts that {@code command} is successfully executed, and
+ * - the command feedback is equal to {@code expectedMessage}
+ * - the {@code FilteredList} is equal to {@code expectedList}
+ * - the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccess(ShowLofCommand command, String expectedMessage, List expectedList) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + CommandResult commandResult = command.execute(); + + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedList, model.getFilteredPersonList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } +} +``` +###### \java\seedu\address\logic\parser\SeekRaCommandParserTest.java +``` java +public class SeekRaCommandParserTest { + + private SeekRaCommandParser parser = new SeekRaCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, SeekRaCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsSeekRaCommand() { + // no leading and trailing whitespaces + SeekRaCommand expectedSeekRaCommand = + new SeekRaCommand(new UnitNumberContainsKeywordsPredicate(Arrays.asList("Alice", "Bob", "RA"))); + assertParseSuccess(parser, " Alice Bob", expectedSeekRaCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " Alice Bob ", expectedSeekRaCommand); + } + +} +``` +###### \java\seedu\address\logic\parser\ShowLofCommandParserTest.java +``` java +public class ShowLofCommandParserTest { + + private ShowLofCommandParser parser = new ShowLofCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ShowLofCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsShowLofCommand() { + // no leading and trailing whitespaces + ShowLofCommand expectedShowLofCommand = + new ShowLofCommand(new LofContainsValuePredicate(Arrays.asList("1", "2"))); + assertParseSuccess(parser, " 1 2", expectedShowLofCommand); + + // multiple whitespaces between index values + assertParseSuccess(parser, " 1 2 ", expectedShowLofCommand); + } + +} +``` +###### \java\seedu\address\model\person\LofContainsValuePredicateTest.java +``` java +public class LofContainsValuePredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + LofContainsValuePredicate firstPredicate = + new LofContainsValuePredicate(firstPredicateKeywordList); + LofContainsValuePredicate secondPredicate = + new LofContainsValuePredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + LofContainsValuePredicate firstPredicateCopy = + new LofContainsValuePredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different person -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_lofContainsValues_returnsTrue() { + // One keyword + LofContainsValuePredicate predicate = + new LofContainsValuePredicate(Collections.singletonList("1")); + assertTrue(predicate.test(new PersonBuilder().withLevelOfFriendship("1").build())); + + // Multiple key values + predicate = new LofContainsValuePredicate(Arrays.asList("1", "3")); + assertTrue(predicate.test(new PersonBuilder().withLevelOfFriendship("1").build())); + + // Only one matching key value + predicate = new LofContainsValuePredicate(Arrays.asList("3", "4")); + assertTrue(predicate.test(new PersonBuilder().withLevelOfFriendship("4").build())); + + } + + @Test + public void test_lofDoesNotContainKeyValues_returnsFalse() { + // Zero keywords + LofContainsValuePredicate predicate = + new LofContainsValuePredicate(Collections.emptyList()); + assertFalse(predicate.test(new PersonBuilder().withLevelOfFriendship("2").build())); + + // Non-matching keyword + predicate = new LofContainsValuePredicate(Arrays.asList("2")); + assertFalse(predicate.test(new PersonBuilder().withLevelOfFriendship("4").build())); + + // Keywords match phone, birthday, level of friendship, unit number, + // but does not match level of friendship + predicate = new LofContainsValuePredicate(Arrays.asList("96667444", "25/03/1997", "3", "#04-28")); + assertFalse(predicate.test(new PersonBuilder().withPhone("96667444") + .withBirthday("25/03/1997").withLevelOfFriendship("4").withUnitNumber("#04-28").build())); + } +} +``` +###### \java\seedu\address\model\person\UnitNumberContainsKeywordsPredicateTest.java +``` java +public class UnitNumberContainsKeywordsPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + UnitNumberContainsKeywordsPredicate firstPredicate = + new UnitNumberContainsKeywordsPredicate(firstPredicateKeywordList); + UnitNumberContainsKeywordsPredicate secondPredicate = + new UnitNumberContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + UnitNumberContainsKeywordsPredicate firstPredicateCopy = + new UnitNumberContainsKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different person -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_nameContainsKeywords_returnsTrue() { + // One keyword + UnitNumberContainsKeywordsPredicate predicate = + new UnitNumberContainsKeywordsPredicate(Collections.singletonList("Alice")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + + // Multiple keywords + predicate = new UnitNumberContainsKeywordsPredicate(Arrays.asList("Alice", "Bob")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + + // Only one matching keyword + predicate = new UnitNumberContainsKeywordsPredicate(Arrays.asList("Bob", "Carol")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice Carol").build())); + + // Mixed-case keywords + predicate = new UnitNumberContainsKeywordsPredicate(Arrays.asList("aLIce", "bOB")); + assertTrue(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + } + + @Test + public void test_nameDoesNotContainKeywords_returnsFalse() { + // Zero keywords + UnitNumberContainsKeywordsPredicate predicate = + new UnitNumberContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new PersonBuilder().withName("Alice").build())); + + // Non-matching keyword + predicate = new UnitNumberContainsKeywordsPredicate(Arrays.asList("Carol")); + assertFalse(predicate.test(new PersonBuilder().withName("Alice Bob").build())); + + // Keywords match phone, birthday, level of friendship, unit number, but does not match name + predicate = new UnitNumberContainsKeywordsPredicate(Arrays.asList("92474733", "23/06/1996", "3", "#4-49")); + assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("92474733") + .withBirthday("23/06/1996").withLevelOfFriendship("3").withUnitNumber("#4-49").build())); + } +} +``` diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index 0f0a8e7ab51e..8e3f2eb32a03 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -3,51 +3,42 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + -{empty} + +CollegeZone was developed by the https://github.com/orgs/CS2103JAN2018-T09-B2/teams/developers[CS2103JAN2018-T09-B2] team. + + + We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] - -Role: Project Advisor - -''' - -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Deborah Low Shi Lei +image::deborahlow97.jpg[width="150", align="left"] +{empty}[https://github.com/deborahlow97[github]] [<>] Role: Team Lead + Responsibilities: UI ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== Goh Zu Wei +image::zuweitrack.jpg[width="150", align="left"] +{empty}[https://github.com/zuweitrack[github]] [<>] Role: Developer + Responsibilities: Data ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Fuad B Sahmawi +image::fuadsahmawi.jpg[width="150", align="left"] +{empty}[https://github.com/fuadsahmawi[github]] [<>] Role: Developer + Responsibilities: Dev Ops + Threading ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Shamsheer Ahamed +image::sham-sheer.jpg[width="150", align="left"] +{empty}[https://github.com/sham-sheer[github]] [<>] Role: Developer + Responsibilities: UI diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index eafdc9574a50..7ca506e6d0ce 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -3,4 +3,4 @@ * *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level4/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. * *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] -* *Email us* : You can also reach us at `damith [at] comp.nus.edu.sg` +* *Email us* : You can also reach us at `e0041985 [at] u.nus.edu` diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 1733af113b29..db76281a6852 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += CollegeZone - Developer Guide :toc: :toc-title: :toc-placement: preamble @@ -10,9 +10,9 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/CS2103JAN2018-T09-B2/main -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `Team T09-B2`      Since: `Mar 2018`      Licence: `MIT` == Setting up @@ -160,7 +160,7 @@ image::UiClassDiagram.png[width="800"] *API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `CalendarPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] @@ -197,7 +197,7 @@ image::DeletePersonSdForLogic.png[width="800"] === Model component .Structure of the Model Component -image::ModelClassDiagram.png[width="800"] +image::ModelComponentCollegeZone.PNG[width="1100"] *API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] @@ -212,7 +212,7 @@ The `Model`, === Storage component .Structure of the Storage Component -image::StorageClassDiagram.png[width="800"] +image::StorageComponentCollegeZone.PNG[width="900"] *API* : link:{repoURL}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] @@ -238,6 +238,7 @@ The undo/redo mechanism is facilitated by an `UndoRedoStack`, which resides insi `UndoRedoStack` only deals with `UndoableCommands`. Commands that cannot be undone will inherit from `Command` instead. The following diagram shows the inheritance diagram for commands: +.Structure of Logic Command image::LogicCommandClassDiagram.png[width="800"] As you can see from the diagram, `UndoableCommand` adds an extra layer between the abstract `Command` class and concrete commands that can be undone, such as the `DeleteCommand`. Note that extra tasks need to be done when executing a command in an _undoable_ way, such as saving the state of the address book before execution. `UndoableCommand` contains the high-level algorithm for those extra tasks while the child classes implements the details of how to execute the specific command. Note that this technique of putting the high-level algorithm in the parent class and lower-level steps of the algorithm in child classes is also known as the https://www.tutorialspoint.com/design_pattern/template_pattern.htm[template pattern]. @@ -277,10 +278,12 @@ Suppose that the user has just launched the application. The `UndoRedoStack` wil The user executes a new `UndoableCommand`, `delete 5`, to delete the 5th person in the address book. The current state of the address book is saved before the `delete 5` command executes. The `delete 5` command will then be pushed onto the `undoStack` (the current state is saved together with the command). +.Initial UndoRedoStack image::UndoRedoStartingStackDiagram.png[width="800"] As the user continues to use the program, more commands are added into the `undoStack`. For example, the user may execute `add n/David ...` to add a new person. +.UndoRedoStack given new command input image::UndoRedoNewCommand1StackDiagram.png[width="800"] [NOTE] @@ -290,6 +293,7 @@ The user now decides that adding the person was a mistake, and decides to undo t We will pop the most recent command out of the `undoStack` and push it back to the `redoStack`. We will restore the address book to the state before the `add` command executed. +.Undo command on UndoRedoStack image::UndoRedoExecuteUndoStackDiagram.png[width="800"] [NOTE] @@ -297,6 +301,7 @@ If the `undoStack` is empty, then there are no other commands left to be undone, The following sequence diagram shows how the undo operation works: +.Sequence diagram for `undo` image::UndoRedoSequenceDiagram.png[width="800"] The redo does the exact opposite (pops from `redoStack`, push to `undoStack`, and restores the address book to the state after the command is executed). @@ -306,14 +311,17 @@ If the `redoStack` is empty, then there are no other commands left to be redone, The user now decides to execute a new command, `clear`. As before, `clear` will be pushed into the `undoStack`. This time the `redoStack` is no longer empty. It will be purged as it no longer make sense to redo the `add n/David` command (this is the behavior that most modern desktop applications follow). +.UndoRedoStack given command `clear` image::UndoRedoNewCommand2StackDiagram.png[width="800"] Commands that are not undoable are not added into the `undoStack`. For example, `list`, which inherits from `Command` rather than `UndoableCommand`, will not be added after execution: +.UndoRedoStack given command `list` image::UndoRedoNewCommand3StackDiagram.png[width="800"] The following activity diagram summarize what happens inside the `UndoRedoStack` when a user executes a new command: +.Activity diagram of UndoRedoStack image::UndoRedoActivityDiagram.png[width="650"] ==== Design Considerations @@ -329,7 +337,7 @@ image::UndoRedoActivityDiagram.png[width="650"] ===== Aspect: How undo & redo executes -* **Alternative 1 (current choice):** Saves the entire address book. +* **Alternative 1 (current choice):** Saves the entire CollegeZone. ** Pros: Easy to implement. ** Cons: May have performance issues in terms of memory usage. * **Alternative 2:** Individual command knows how to undo/redo by itself. @@ -339,7 +347,7 @@ image::UndoRedoActivityDiagram.png[width="650"] ===== Aspect: Type of commands that can be undone/redone -* **Alternative 1 (current choice):** Only include commands that modifies the address book (`add`, `clear`, `edit`). +* **Alternative 1 (current choice):** Only include commands that modifies the CollegeZone (`add`, `clear`, `edit`). ** Pros: We only revert changes that are hard to change back (the view can easily be re-modified as no data are * lost). ** Cons: User might think that undo also applies when the list is modified (undoing filtering for example), * only to realize that it does not do that, after executing `undo`. * **Alternative 2:** Include all commands. @@ -365,6 +373,7 @@ _{Explain here how the data encryption feature will be implemented}_ // end::dataencryption[] +// tag::logging[] === Logging We are using `java.util.logging` package for logging. The `LogsCenter` class is used to manage the logging levels and logging destinations. @@ -380,489 +389,1838 @@ We are using `java.util.logging` package for logging. The `LogsCenter` class is * `INFO` : Information showing the noteworthy actions by the App * `FINE` : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size +// end::logging[] + [[Implementation-Configuration]] === Configuration Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file (default: `config.json`). -== Documentation +=== Enhanced Find Command [Since v1.1] -We use asciidoc for writing documentation. +The old find command feature only allows searching by name. To make CollegeZone more useful for RC4 students, we have enhanced the find +command feature to be able to find persons by tags. -[NOTE] -We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting. +==== Aspect: User Input +* Old user input format: find +* New user input format: find n/ t/ -=== Editing Documentation +==== Aspect: Nature of user input +* Searching of name and tag at the same time is not allowed +* If user is searching by name, user input should be: find n/ t/ +* If user is searching by tags, user input should be: find n/ t/ -See <> to learn how to render `.adoc` files locally to preview the end result of your edits. -Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your `.adoc` files in real-time. +=== Command Aliases [Since v1.1] -=== Publishing Documentation +CollegeZone users may now use shortcuts to perform desired tasks. These shortcuts are shown in the table below. -See <> to learn how to deploy GitHub Pages using Travis. -=== Converting Documentation to PDF format +[width="90%",cols="20%,<25%,<25%",options="header",] +|======================================================================= +|Command | Original| Alias +|Add +|add +|a -We use https://www.google.com/chrome/browser/desktop/[Google Chrome] for converting documentation to PDF format, as Chrome's PDF engine preserves hyperlinks used in webpages. +|Clear +|clear +|c -Here are the steps to convert the project documentation files to PDF format. +|Delete +|delete +|d -. Follow the instructions in <> to convert the AsciiDoc files in the `docs/` directory to HTML format. -. Go to your generated HTML files in the `build/docs` folder, right click on them and select `Open with` -> `Google Chrome`. -. Within Chrome, click on the `Print` option in Chrome's menu. -. Set the destination to `Save as PDF`, then click `Save` to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below. +|Edit +|edit +|e -.Saving documentation as PDF files in Chrome -image::chrome_save_as_pdf.png[width="300"] +|Find +|find +|f -[[Testing]] -== Testing +|History +|history +|h -=== Running Tests +|List +|list +|l -There are three ways to run tests. +|Rate +|rate +|rt -[TIP] -The most reliable way to run tests is the 3rd one. The first two methods might fail some GUI tests due to platform/resolution-specific idiosyncrasies. +|Redo +|redo +|r -*Method 1: Using IntelliJ JUnit test runner* +|Seek +|seek +|sk -* To run all tests, right-click on the `src/test/java` folder and choose `Run 'All Tests'` -* To run a subset of tests, you can right-click on a test package, test class, or a test and choose `Run 'ABC'` +|Select +|select +|s -*Method 2: Using Gradle* +|Show +|show +|sh -* Open a console and run the command `gradlew clean allTests` (Mac/Linux: `./gradlew clean allTests`) +|Undo +|undo +|u -[NOTE] -See <> for more info on how to run tests using Gradle. +|Meet +|meet +|m -*Method 3: Using Gradle (headless)* +|======================================================================= -Thanks to the https://github.com/TestFX/TestFX[TestFX] library we use, our GUI tests can be run in the _headless_ mode. In the headless mode, GUI tests do not show up on the screen. That means the developer can do other things on the Computer while the tests are running. +//end::find[] -To run tests in headless mode, open a console and run the command `gradlew clean headless allTests` (Mac/Linux: `./gradlew clean headless allTests`) +// tag::addandedit[] +=== Targeted add and edit command [Since v1.2] +As CollegeZone is designed for NUS RC4 students to use, being able to record other students Level of Friendship, + Birthday, RC4 Unit Number and RC4 CCAs, will be a useful feature for them. -=== Types of tests +As CollegeZone is catered toward NUS RC4 Residents, we have changed the attributes of a Person to hold: + +*Name, Mobile Number, Birthday, Level of Friendship, RC4 Unit Number, Meet up dates with RC4 students, RC4 CCAs and Tags*. + +This is done by removing unwanted attributes of a person and adding new attributes of a person. + +The figure below shows the new atrributes for student in the class diagram. -We have two types of tests: +.Class diagram for Student +image::RC4ModelComponenetClass.JPG[width="800"] -. *GUI Tests* - These are tests involving the GUI. They include, -.. _System Tests_ that test the entire App by simulating user actions on the GUI. These are in the `systemtests` package. -.. _Unit tests_ that test the individual components. These are in `seedu.address.ui` package. -. *Non-GUI Tests* - These are tests not involving the GUI. They include, -.. _Unit tests_ targeting the lowest level methods/classes. + -e.g. `seedu.address.commons.StringUtilTest` -.. _Integration tests_ that are checking the integration of multiple code units (those code units are assumed to be working). + -e.g. `seedu.address.storage.StorageManagerTest` -.. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together. + -e.g. `seedu.address.logic.LogicManagerTest` +==== Design Considerations +*Aspect*: Displaying level of friendship in `CollegeZone` UI. +*Alternative 1(current choice)*: `Level of Friendship` is displayed as a string of heart symbols. + +*Pros*: Looks fanciful to user. + +*Cons*: Might not be intuitive for the user to understand the meaning of heart symbols. -=== Troubleshooting Testing -**Problem: `HelpWindowTest` fails with a `NullPointerException`.** +*Alternative 2*: `Level of Friendship` is displayed as a number. + + +*Pros*: User easily understands the meaning of it. + +*Cons*: Less eye catching to the user. -* Reason: One of its dependencies, `UserGuide.html` in `src/main/resources/docs` is missing. -* Solution: Execute Gradle task `processResources`. +--- +// end::addandedit[] -== Dev Ops +// tag::reminder[] +=== Reminders [Since v1.4] -=== Build Automation +==== Introduction + -See <> to learn how to use Gradle for build automation. +RC4 students will have a very busy schedule that consists of tasks, events & activities. + +Hence, we decided on implementing a reminder feature to allow them to add & delete reminders in CollegeZone to assist them in organising their schedule. + -=== Continuous Integration +The `AddReminderCommand` allows you to add a `Reminder` into CollegeZone and is stored in an ArrayList, `UniqueReminderList`, in `AddressBook`. +The `DeleteReminderCommand` allows you to delete a `Reminder` from CollegeZone. -We use https://travis-ci.org/[Travis CI] and https://www.appveyor.com/[AppVeyor] to perform _Continuous Integration_ on our projects. See <> and <> for more details. +Reminder commands are undoable and redoable for the benefit users to redo and undo a command they did or did not intend to change. + +Hence, both `AddReminderCommand` and `DeleteReminderCommand` are implemented as `UndoableCommand`. -=== Coverage Reporting +*Reminder Features:* + -We use https://coveralls.io/[Coveralls] to track the code coverage of our projects. See <> for more details. +* Adding a reminder to the Calendar: The `AddReminderCommand` allows you to add a `Reminder` into CollegeZone and is stored in an ArrayList, `UniqueReminderList`, in `AddressBook`. +* Deleting a reminder on the Calendar: The `DeleteReminderCommand` allows you to delete a `Reminder` from `UniqueReminderList`, in `AddressBook`. -=== Documentation Previews -When a pull request has changes to asciidoc files, you can use https://www.netlify.com/[Netlify] to see a preview of how the HTML version of those asciidoc files will look like when the pull request is merged. See <> for more details. +Reminder commands are undoable and redoable for the benefit users to redo and undo a command they did or did not intend to change. +Hence, both `AddReminderCommand` and `DeleteReminderCommand` are implemented as `UndoableCommand`. -=== Making a Release +==== Implementation + -Here are the steps to create a new release. +*Model Component* + -. Update the version number in link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp.java`]. -. Generate a JAR file <>. -. Tag the repo with the version number. e.g. `v0.1` -. https://help.github.com/articles/creating-releases/[Create a new release using GitHub] and upload the JAR file you created. +* Reminder Class -=== Managing Dependencies +Every time a `Reminder` is created, three other objects are also created: + -A project often depends on third-party libraries. For example, Address Book depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + -a. Include those libraries in the repo (this bloats the repo size) + -b. Require developers to download those libraries manually (this creates extra work for developers) +1. `ReminderText`: This object contains a single string variable, `reminderText`, that is verified to contain characters and spaces and cannot be blank. + +2. `DateTime`: This object contains a single string variable, `dateTime`. After obtaining the start date time from user input, it will parse through `nattyDateAndTimeParser` to convert it to a `LocalDateTime` +variable. Subsequently, this `LocalDateTime` variable will then be converted back to a string variable using `properReminderDateTimeFormat` and it stored as `dateTime` in `DateTime` object. + +3. `EndDateTime`: This object contains a single string variable, `dateTime`. After obtaining the end date time from user input, it will parse through `nattyDateAndTimeParser` to convert it to a `LocalDateTime` + variable. Subsequently, this `LocalDateTime` variable will then be converted back to a string variable using `properReminderDateTimeFormat` and it stored as `dateTime` in `DateTime` object. -[[GetStartedProgramming]] -[appendix] -== Suggested Programming Tasks to Get Started +A `Reminder` will be marked with a *blue* circle if it's not due and be marked with a *red* circle if it's due. + -Suggested path for new programmers: +Users can delete reminders on the Calendar if its already due or when they accidentally made a mistake. + -1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in <>. +._Class Diagram of Reminder_ +image::ReminderClassDiagram.PNG[width="800"] -2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. <> explains how to go about adding such a feature. +* UniqueReminderList + -[[GetStartedProgramming-EachComponent]] -=== Improving each component +`UniqueReminderList` functions as a List of `Reminders` where every element is unique and is defined by its `ReminderText`, +`DateTime` and `EndDateTime`. -Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work). +*Logic Component* + -[discrete] -==== `Logic` component +* Adding a Reminder + -*Scenario:* You are in charge of `logic`. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases. +When `AddReminderCommand` is executed, it first checks whether there are any duplicate reminders in `UniqueReminderList`. If there is no duplicate reminder, +`Reminder` is added into `UniqueReminderList` in `AddressBook`. -[TIP] -Do take a look at <> before attempting to modify the `Logic` component. +.Interactions Inside the Logic Component for the `+r text/eat pills d/tmr 8pm e/tmr 10pm` Command +image::addReminderSeqDiagram.PNG[width="800"] -. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing `clear`, the user can also type `c` to remove all persons in the list. -+ -**** -* Hints -** Just like we store each individual command word constant `COMMAND_WORD` inside `*Command.java` (e.g. link:{repoURL}/src/main/java/seedu/address/logic/commands/FindCommand.java[`FindCommand#COMMAND_WORD`], link:{repoURL}/src/main/java/seedu/address/logic/commands/DeleteCommand.java[`DeleteCommand#COMMAND_WORD`]), you need a new constant for aliases as well (e.g. `FindCommand#COMMAND_ALIAS`). -** link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] is responsible for analyzing command words. -* Solution -** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute the same intended command. -** Add new tests for each of the aliases that you have added. -** Update the user guide to document the new aliases. -** See this https://github.com/se-edu/addressbook-level4/pull/785[PR] for the full solution. -**** +* Deleting a Reminder + -[discrete] -==== `Model` component +When `DeleteReminderCommand` is executed, it will find the `Reminder` specified by the user using parameters `ReminderText` and `DateTime`. If `Reminder` specified by user +is not found in `UniqueReminderList`, `CommandException` will be thrown. If `Reminder` is found, it will then be removed from `UniqueReminderList`. The code snippet to find and remove the `Reminder` +specified by user is shown below. Code snippet of this is shown in Code Snippet 3.9.1. -*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. +.Interactions Inside the Logic Component for the `-r text/eat pills d/tmr 8pm` Command +image::delReminderSeqDiagram.PNG[width="800"] -[TIP] -Do take a look at <> before attempting to modify the `Model` component. - -. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the address book. -+ -**** -* Hints -** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] API need to be updated. -** Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags? -** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] allows you to update a person, and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] allows you to update the tags. -* Solution -** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. Loop through each person, and remove the `tag` from each person. -** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`] should call `AddressBook#removeTag(Tag)`. -** Add new tests for each of the new public methods that you have added. -** See this https://github.com/se-edu/addressbook-level4/pull/790[PR] for the full solution. -*** The current codebase has a flaw in tags management. Tags no longer in use by anyone may still exist on the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. This may cause some tests to fail. See issue https://github.com/se-edu/addressbook-level4/issues/753[`#753`] for more information about this flaw. -*** The solution PR has a temporary fix for the flaw mentioned above in its first commit. -**** +[source,java] +---- +@Override + protected void preprocessUndoableCommand() throws CommandException { + model.updateFilteredReminderList(predicate); + List lastShownList = model.getFilteredReminderList(); + targetIndex = Index.fromOneBased(1); + if (lastShownList.size() > 1) { + for (Reminder reminder : lastShownList) { + if (reminder.getDateTime().toString().equals(dateTime)) { + reminderToDelete = reminder; + } + } + } else { + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_REMINDER_TEXT_DATE); + } + + reminderToDelete = lastShownList.get(targetIndex.getZeroBased()); + } + } +---- +.Code Snippet 3.9.1: Method to find specific Reminder to delete -[discrete] -==== `Ui` component +*User Interface(Syncing Calendar to Reminders)* + -*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your address book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn't prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems. +To display the reminder in the calendar, we have a `CalendarPanel` that takes in the `UniqueReminderList`. -[TIP] -Do take a look at <> before attempting to modify the `UI` component. +[source,java] +---- + public CalendarPanel(ObservableList reminderList, ObservableList personList) { + super(FXML); -. Use different colors for different tags inside person cards. For example, `friends` tags can be all in brown, and `colleagues` tags can be all in yellow. -+ -**Before** -+ -image::getting-started-ui-tag-before.png[width="300"] -+ -**After** -+ -image::getting-started-ui-tag-after.png[width="300"] -+ -**** -* Hints -** The tag labels are created inside link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[the `PersonCard` constructor] (`new Label(tag.tagName)`). https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Label.html[JavaFX's `Label` class] allows you to modify the style of each Label, such as changing its color. -** Use the .css attribute `-fx-background-color` to add a color. -** You may wish to modify link:{repoURL}/src/main/resources/view/DarkTheme.css[`DarkTheme.css`] to include some pre-defined colors using css, especially if you have experience with web-based css. -* Solution -** You can modify the existing test methods for `PersonCard` 's to include testing the tag's color as well. -** See this https://github.com/se-edu/addressbook-level4/pull/798[PR] for the full solution. -*** The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes. -**** + this.reminderList = reminderList; + this.personList = personList; -. Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] such that link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] can show a different style on error (currently it shows the same regardless of errors). -+ -**Before** -+ -image::getting-started-ui-result-before.png[width="200"] -+ -**After** -+ -image::getting-started-ui-result-after.png[width="200"] -+ -**** -* Hints -** link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] is raised by link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] which also knows whether the result is a success or failure, and is caught by link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] which is where we want to change the style to. -** Refer to link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] for an example on how to display an error. -* Solution -** Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] 's constructor so that users of the event can indicate whether an error has occurred. -** Modify link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)`] to react to this event appropriately. -** You can write two different kinds of tests to ensure that the functionality works: -*** The unit tests for `ResultDisplay` can be modified to include verification of the color. -*** The system tests link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest#assertCommandBoxShowsDefaultStyle() and AddressBookSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. -** See this https://github.com/se-edu/addressbook-level4/pull/799[PR] for the full solution. -*** Do read the commits one at a time if you feel overwhelmed. -**** + calendarView = new CalendarView(); + setupCalendar(); + updateCalendar(); + registerAsAnEventHandler(this); + } +---- +.Code Snippet 3.9.2: Inititialisation of Calendar Panel for User Interface -. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the address book. -+ -**Before** -+ -image::getting-started-ui-status-before.png[width="500"] -+ -**After** -+ -image::getting-started-ui-status-after.png[width="500"] -+ -**** -* Hints -** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! -** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated. -* Solution -** Modify the constructor of link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of persons when the application just started. -** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleAddressBookChangedEvent(AddressBookChangedEvent)`] to update the number of persons whenever there are new changes to the addressbook. -** For tests, modify link:{repoURL}/src/test/java/guitests/guihandles/StatusBarFooterHandle.java[`StatusBarFooterHandle`] by adding a state-saving functionality for the total number of people status, just like what we did for save location and sync status. -** For system tests, modify link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest`] to also verify the new total number of persons status bar. -** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. -**** +`UniqueReminderList` will then be iterated and each reminder in the list is individually added into the calendar through `updateCalendar()`. Every time a new reminder is added into `CollegeZone`, an event handler, `handleNewCalendarEvent`, will +cause `calendarUpdate()` to run again and `CalendarPanel` will be updated to display the new reminder added onto `CollegeZone`. -[discrete] -==== `Storage` component +[source,java] +---- + @Subscribe + private void handleNewCalendarEvent(AddressBookChangedEvent event) { + reminderList = event.data.getReminderList(); + personList = event.data.getPersonList(); + Platform.runLater(this::updateCalendar); + } +---- +.Code Snippet 3.9.3: Event Handler: handleNewCalendarEvent -*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage. +[source,java] +---- -[TIP] -Do take a look at <> before attempting to modify the `Storage` component. + /** + * Updates the Calendar with Reminders that are already added + */ + private void updateCalendar() { + setDateAndTime(); + CalendarSource myCalendarSource = new CalendarSource("Reminders and Meetups"); + Calendar calendarRDue = new Calendar("Reminders Already Due"); + Calendar calendarRNotDue = new Calendar("Reminders Not Due"); + Calendar calendarM = new Calendar("Meetups"); + calendarRDue.setStyle(Calendar.Style.getStyle(4)); + calendarRDue.setLookAheadDuration(Duration.ofDays(365)); + calendarRNotDue.setStyle(Calendar.Style.getStyle(1)); + calendarRNotDue.setLookAheadDuration(Duration.ofDays(365)); + calendarM.setStyle(Calendar.Style.getStyle(3)); + myCalendarSource.getCalendars().add(calendarRDue); + myCalendarSource.getCalendars().add(calendarRNotDue); + myCalendarSource.getCalendars().add(calendarM); + for (Reminder reminder : reminderList) { + LocalDateTime ldtstart = nattyDateAndTimeParser(reminder.getDateTime().toString()).get(); + LocalDateTime ldtend = nattyDateAndTimeParser(reminder.getEndDateTime().toString()).get(); + LocalDateTime now = LocalDateTime.now(); + if (now.isBefore(ldtend)) { + calendarRNotDue.addEntry(new Entry( + reminder.getReminderText().toString(), new Interval(ldtstart, ldtend))); + } else { + calendarRDue.addEntry(new Entry(reminder.getReminderText().toString(), new Interval(ldtstart, ldtend))); + } + } + calendarView.getCalendarSources().add(myCalendarSource); + } +---- +.Code Snippet 3.9.4: _updateCalendar() method_ -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. -+ -**** -* Hint -** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/AddressBookStorage.java[`AddressBookStorage`] interface. -** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/XmlAddressBookStorage.java[`XmlAddressBookStorage`] class. -* Solution -** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. -**** +When a reminder is deleted, it will go through the same process as adding reminder and the changes will then be updated in the calendar. + +==== Design Considerations + +*Aspect*: Deleting a `Reminder` from `CollegeZone`. + +*Alternative 1*: Delete `Reminder` using an index which is the index of the particular `Reminder` in `UniqueReminderList` + +*Pros*: Implementing `DeleteReminderCommand` by parsing an index will be simple as `DeleteCommand` to delete a person from `AddressBook` is using a similar implementation. + +*Cons*: We will have to first implement a list function to list all reminders with their respective indexes, which may be undesirable as there may be +a large number of reminders to be listed out. This will in turn require the need of a find function to find a specific reminder that the users want to delete. + +*Alternative 2(current choice)*: Delete `Reminder` identified by `ReminderText` and `DateTime`. + +*Pros*: Reduces the need of a listing and finding function to delete a `Reminder` from `CollegeZone`. + +*Cons*: Implementation of `DeleteReminderCommand` will be more difficult as we will have to integrate a find function to pick out +the specific `Reminder` that the user wants to remove. + +--- +// end::reminder[] + +// tag::Goals[] +=== Goal Object [Since v1.2] +CollegeZone is designed for RC4 students to use. RC4 students often have goals that they want to achieve in life +– Career goals, health goals, social goals, relationship goals etc. This additional goal feature is created for RC4 users to add and keep track of their goals throughout their stay. +The main reason behind this implementation is because setting goals gives you *long-term vision* and *short-term motivation* for the goals. +This implementation allows RC4 students to set goals in _CollegeZone_ – big or small ones - so + that they will be reminded of the goals that they have set for themselves. + +*Goal features:* +``` + 1. add goal + 2. edit goal + 3. delete goal + 4. complete goal + 5. ongoing goal + 6. sort goal +``` +==== Implementation of Goal Object + +*Goal objects consists of 5 attributes :* + + +1. Date and time of when goal is completed. + +2. Level of importance of goal. + +3. Text content of Goal. + +4. Date and time of Goal of when goal has started. + +5. Goal completion status. + +._Class diagram of Goal_ +image::CollegeZoneGoalModelClassDiagram.JPG[width="750"] + +The code snippet shown below shows the overloading of StartDateTime constructor class. +It keeps both a String value and a LocalDateTime value. +The _Code-snippet 2 and 3_ shows the conversion of the String value to LocalDateTime value and vice versa. + +[source,java] +---- +public class StartDateTime implements Comparable { + + public final String value; + public final LocalDateTime localDateTimeValue; + + public StartDateTime(LocalDateTime startDateTime) { + requireNonNull(startDateTime); + this.localDateTimeValue = startDateTime; + this.value = properDateTimeFormat(startDateTime); + } + + public StartDateTime(String startDateTimeInString) { + requireNonNull(startDateTimeInString); + this.value = startDateTimeInString; + this.localDateTimeValue = getLocalDateTimeFromProperDateTime(startDateTimeInString); + } +} +---- +.Code Snippet 3.10.1: StartDateTime method -[[GetStartedProgramming-RemarkCommand]] -=== Creating a new command: `remark` +[source,java] +---- + public static String properDateTimeFormat(LocalDateTime dateTime) { + StringBuilder builder = new StringBuilder(); + int day = dateTime.getDayOfMonth(); + String month = dateTime.getMonth().getDisplayName(TextStyle.FULL, Locale.ENGLISH); + int year = dateTime.getYear(); + int hour = dateTime.getHour(); + int minute = dateTime.getMinute(); + builder.append("Date: ") + .append(day) + .append(" ") + .append(month) + .append(" ") + .append(year) + .append(", Time: ") + .append(String.format("%02d", hour)) + .append(":") + .append(String.format("%02d", minute)); + return builder.toString(); + } +---- +.Code Snippet 3.10.2: Method conversion -By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app. +[source,java] +---- + public static LocalDateTime getLocalDateTimeFromProperDateTime(String properDateTimeString) { + String trimmedArgs = properDateTimeString.trim(); + int size = trimmedArgs.length(); + String stringFormat = properDateTimeString.substring(BEGIN_INDEX, size); + stringFormat = stringFormat.replace(", Time: ", ""); + return nattyDateAndTimeParser(stringFormat).get(); + } +---- +.Code Snippet 3.10.3: Method conversion -*Scenario:* You are a software maintainer for `addressbook`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. +- All goals will have a string of stars (indicating importance) in a yellow border directly below the goal text in the goal list panel. -==== Description -Edits the remark for a person specified in the `INDEX`. + -Format: `remark INDEX r/[REMARK]` -Examples: +==== Design Considerations -* `remark 1 r/Likes to drink coffee.` + -Edits the remark for the first person to `Likes to drink coffee.` -* `remark 1 r/` + -Removes the remark for the first person. +**Aspect:** Representation of Goals level of importance in UI + +**Alternative 1 (current choice):** Each level of importance have a number of stars related to it. + +**Pros:** Ability for the user to differentiate the Goals with higher level of importance compared to those with lower level of importance. + +**Cons:** The goal list in the UI might look messy to the user without having a sort Goals option as the list of goals is displayed based on when it was added. + +**Alternative 2:** Having an additional sort goal command + +**Pros:** It is simple and easy to understand. + +**Cons:** It requires extra methods to implement the sort function. + -==== Step-by-step Instructions +--- +// end::Goals[] -===== [Step 1] Logic: Teach the app to accept 'remark' which does nothing -Let's start by teaching the application how to parse a `remark` command. We will add the logic of `remark` later. +// tag::addgoal[] +=== Add goal mechanism [Since v1.3] -**Main:** +Adding a goal into _CollegeZone_ is facilitated by `AddGoalCommand`, which extends `UndoableCommand`, it +supports undoing and redoing of commands that modifies the state of the _CollegeZone_. -. Add a `RemarkCommand` that extends link:{repoURL}/src/main/java/seedu/address/logic/commands/UndoableCommand.java[`UndoableCommand`]. Upon execution, it should just throw an `Exception`. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to accept a `RemarkCommand`. +[source,java] +---- +public class AddGoalCommand extends UndoableCommand { +@Override + public CommandResult executeUndoableCommand() throws CommandException { + // ... AddGoalCommand logic ... + } +} +---- -**Tests:** +The following sequence diagram shows the flow of operation from the point _CollegeZone_ receives an input to the output of the result. -. Add `RemarkCommandTest` that tests that `executeUndoableCommand()` throws an Exception. -. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. +._Interactions Inside the Logic Component for the `+g` Command_ +image::AddGoalSeqDiagram.PNG[width="800"] -===== [Step 2] Logic: Teach the app to accept 'remark' arguments -Let's teach the application to parse arguments that our `remark` command will accept. E.g. `1 r/Likes to drink coffee.` +AddGoalCommand is implemented in this way: -**Main:** +In the Logic component, `AddressBookParser` will parse the user's input and detects if add goal keyword contains correct parsing keywords after. +For example, e.g. +g text/eat healthily impt/9 +`AddGoalCommandParser` parses the input by extracting the input text and importance, + +e.g.Parsed text : eat healthily + + Parsed importance : 9 + +Everytime a goal is added, the start date time of the goal will be recorded down in real time and it's completion +status will be "ongoing" by default. +`AddGoalCommandParser` is implemented in this way: +[source,java] +---- +public class AddGoalCommandParser implements Parser { + + public static final String EMPTY_END_DATE_TIME = ""; + public static final boolean INITIAL_COMPLETION_STATUS = false; + + public AddGoalCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_IMPORTANCE, PREFIX_GOAL_TEXT); + + if (!arePrefixesPresent(argMultimap, PREFIX_IMPORTANCE, PREFIX_GOAL_TEXT) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddGoalCommand.MESSAGE_USAGE)); + } + try { + Importance importance = ParserUtil.parseImportance(argMultimap.getValue(PREFIX_IMPORTANCE)).get(); + GoalText goalText = ParserUtil.parseGoalText(argMultimap.getValue(PREFIX_GOAL_TEXT)).get(); + StartDateTime startDateTime = new StartDateTime(LocalDateTime.now()); + EndDateTime endDateTime = new EndDateTime(EMPTY_END_DATE_TIME); + Completion completion = new Completion(INITIAL_COMPLETION_STATUS); + Goal goal = new Goal(importance, goalText, startDateTime, endDateTime, completion); + return new AddGoalCommand(goal); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } +} +---- +The `AddGoalCommandParser` returns `AddGoalCommand` after execution, passes the text and importance and string as arguments which will be further processed by `logic` component. +`AddGoalCommand` adds the new goal to the list locally and runs the execution which calls the model. +In the Model component, the `UniqueGoalList` is called and the new goal is added to the list. +In the Ui component, the new goal added is displayed in the goal list panel. +In the Storage component, the new goal added is stored in the address book storage -. Modify `RemarkCommand` to take in an `Index` and `String` and print those two parameters as the error message. -. Add `RemarkCommandParser` that knows how to parse two arguments, one index and one with prefix 'r/'. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to use the newly implemented `RemarkCommandParser`. +Note: +- A goal that has just been added will be placed at the bottom of the goal list. + -**Tests:** +==== Design Considerations -. Modify `RemarkCommandTest` to test the `RemarkCommand#equals()` method. -. Add `RemarkCommandParserTest` that tests different boundary values -for `RemarkCommandParser`. -. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`] to test that the correct command is generated according to the user input. +**Aspect:** Implementation of adding start date time and completion status of goal + +**Alternative 1 (current choice):** Having the current date time as the start date time and having a default completion status of a goal added. + +**Pros:** User have lesser words to input in the command box. + +**Cons:** User might have a preferred start date time and completion status of the goal that they just added. + +**Alternative 2:** Giving user a choice of start date time input and completion status of goal added. + +**Pros:** Allows user to choose their own start date time and completion status. + +**Cons:** Tedious for user to input a longer add goal command and slightly more difficult to properly parse the start date time + that user enters. -===== [Step 3] Ui: Add a placeholder for remark in `PersonCard` -Let's add a placeholder on all our link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] s to display a remark for each person later. +--- -**Main:** +**Aspect:** Representation of Goals in UI + +**Alternative 1 (current choice):** Having a goal list panel beside our current person list panel. + +**Pros:** Ability for the user to differentiate the Goals with higher level of importance compared to those with lower level of importance. + +**Cons:** The initial space in UI reserved for person list is not being used to display 2 lists, the person list and the goal list. This causes the UI to look clunky and overwhelming. + +**Alternative 2:** Having a tab button in CollegeZone that allows user to switch between the person list panel and goal list panel. + +**Pros:** Ability for user to switch to person list and goal list in the UI, which makes it look more user friendly. + +**Cons:** As CollegeZone is a desktop application that has most interactions happen using a Command Line Interface (CLI), a new command to switch tabs between goal list and person list needs to be implemented. + -. Add a `Label` with any random text inside link:{repoURL}/src/main/resources/view/PersonListCard.fxml[`PersonListCard.fxml`]. -. Add FXML annotation in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] to tie the variable to the actual label. +--- +// end::addgoal[] -**Tests:** -. Modify link:{repoURL}/src/test/java/guitests/guihandles/PersonCardHandle.java[`PersonCardHandle`] so that future tests can read the contents of the remark label. +// tag::deletegoal[] +=== Delete goal mechanism [Since v1.4] -===== [Step 4] Model: Add `Remark` class -We have to properly encapsulate the remark in our link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] class. Instead of just using a `String`, let's follow the conventional class structure that the codebase already uses by adding a `Remark` class. +Deleting a goal from _CollegeZone_ is facilitated by `DeleteGoalCommand`, which extends `UndoableCommand`, it +supports undoing and redoing of commands that modifies the state of the _CollegeZone_. -**Main:** +[source,java] +---- +public class DeleteGoalCommand extends UndoableCommand { +@Override + public CommandResult executeUndoableCommand() throws CommandException { + // ... DeleteGoalCommand logic ... + } +} +---- -. Add `Remark` to model component (you can copy from link:{repoURL}/src/main/java/seedu/address/model/person/Address.java[`Address`], remove the regex and change the names accordingly). -. Modify `RemarkCommand` to now take in a `Remark` instead of a `String`. +DeleteGoalCommand is implemented in this way: -**Tests:** +In the Logic component, `AddressBookParser` will parse the user's input, for example, `-goal 1` and detects the `INDEX` given by the user . +`DeleteGoalCommandParser` parses the `INDEX`: + +e.g.Parsed index : 1 + -. Add test for `Remark`, to test the `Remark#equals()` method. +._Interactions Inside the Logic Component for the `-goal` Command_ +image::DeleteGoalSeqDiagram.PNG[width="800"] -===== [Step 5] Model: Modify `Person` to support a `Remark` field -Now we have the `Remark` class, we need to actually use it inside link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +`DeleteGoalCommandParser` creates a `DeleteGoalCommand` class and passes the index as argument. +It deletes the goal corresponding to the index locally and runs the execution which calls the model +The filtered goals list and deletion of the specified Goal object occurs. The filtered goals list is updated and retrieved. +The UI component displays the deletion of goal in the goal list panel and the storage component deletes the details of the deleted goal in the address book storage. -**Main:** +==== Design Considerations -. Add `getRemark()` in link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. -. You may assume that the user will not be able to use the `add` and `edit` commands to modify the remarks field (i.e. the person will be created without a remark). -. Modify link:{repoURL}/src/main/java/seedu/address/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks for the sample data (delete your `addressBook.xml` so that the application will load the sample data when you launch it.) +**Aspect:** Deciding the field for user to enter to delete the goal + +**Alternative 1 (current choice):** Using `INDEX` of goals in goal list panel + +**Pros:** Easier to implement as it's implementation is similar to deleting a student and easier for the user to type in the command as `INDEX` is an integer + +**Cons:** User might get confused as we have 2 list panels with `INDEX` + +**Alternative 2: ** Using `GOAL_TEXT` of goals in goal list panel + +**Pros:** Lower chance for user to use delete goal command wrongly + +**Cons:** User have to type in a longer command -===== [Step 6] Storage: Add `Remark` field to `XmlAdaptedPerson` class -We now have `Remark` s for `Person` s, but they will be gone when we exit the application. Let's modify link:{repoURL}/src/main/java/seedu/address/storage/XmlAdaptedPerson.java[`XmlAdaptedPerson`] to include a `Remark` field so that it will be saved. +--- +// end::deletegoal[] -**Main:** +// tag::sortgoal[] +=== Sort goal mechanism [Since v1.5] -. Add a new Xml field for `Remark`. +The sorting goal mechanism is facilitated by `SortGoalCommandParser` and `SortGoalCommand`, with both classes residing +in the `Logic` component of CollegeZone. `SortGoalCommand` in not undoable. -**Tests:** +`SortGoalCommandParser` takes in an arguments in the form of `FIELD` and `ORDER` that defines how `UniqueGoalList` +should be sorted. You may customise the sort goal operation, with `FIELD` specifying the goal field to sort and `ORDER` +specifying the type of sort order. It checks for validity against a few keywords. For example, `ORDER` only accepts 2 keywords: `ascending` or `descending`. +On the other hand, `FIELD` only accepts 3 keywords: `importance`, `startdatetime` and `completion`. -. Fix `invalidAndValidPersonAddressBook.xml`, `typicalPersonsAddressBook.xml`, `validAddressBook.xml` etc., such that the XML tests will not fail due to a missing `` element. +The image below shows the interaction of sort the `Logic` component. -===== [Step 6b] Test: Add withRemark() for `PersonBuilder` -Since `Person` can now have a `Remark`, we should add a helper method to link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`], so that users are able to create remarks when building a link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +.Interactions Inside the Logic Component for the `sortgoal importance o/ascending` Command +image::SortGoalSeqDiagram.PNG[width="800"] -**Tests:** +Upon execution of `SortGoalCommand`, a `Comparator` will be initialised based on the sort type it receives. +A `sortGoal` function call will be made to `Model`, which propagates down to `UniqueGoalList`, where the sorting of the `internalList` occurs. + +The code below shows the switch statement used to define the type of sort the user wants in the `UniqueGoalList`. + -. Add a new method `withRemark()` for link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`]. This method will create a new `Remark` for the person that it is currently building. -. Try and use the method on any sample `Person` in link:{repoURL}/src/test/java/seedu/address/testutil/TypicalPersons.java[`TypicalPersons`]. +[source,java] +---- +public class UniqueGoalList implements Iterable { + + public void sortGoal(String sortField, String sortOrder) throws EmptyGoalListException { + String sortFieldAndOrder = sortField + " " + sortOrder; + //Comparator comparatorImportance = Comparator.comparingInt(Goal::getImportance); + switch (sortFieldAndOrder) { + case "importance ascending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) ->goalA.getImportance() + .compareTo(goalB.getImportance())); + break; + case "importance descending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) ->goalB.getImportance() + .compareTo(goalA.getImportance())); + break; + case "completion ascending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) -> new Boolean(goalA.getCompletion().hasCompleted) + .compareTo(goalB.getCompletion().hasCompleted)); + break; + case "completion descending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) -> new Boolean(goalB.getCompletion().hasCompleted) + .compareTo(goalA.getCompletion().hasCompleted)); + break; + case "startdatetime ascending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) ->goalA.getStartDateTime() + .compareTo(goalB.getStartDateTime())); + break; + case "startdatetime descending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) ->goalB.getStartDateTime() + .compareTo(goalA.getStartDateTime())); + break; + + default: + break; + } + } +} +---- +Once verified, the argument will be tokenized to identify your specified sort type. A `SortGoalCommand` object is then +created with the identified sort type. +[NOTE] +If the goal list is found to be empty, a `CommandException` will be thrown from `SortGoalCommand`. -===== [Step 7] Ui: Connect `Remark` field to `PersonCard` -Our remark label in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] is still a placeholder. Let's bring it to life by binding it with the actual `remark` field. +==== Design Considerations -**Main:** +**Aspect:** Deciding the fields and orders that the goal list can be sorted + +**Alternative 1:** Sort only one field and in one order + +**Pros:** Sort becomes a more intuitive command for user to type + +**Cons:** Less customization of sort goal command. + +**Alternative 2 (current choice): ** Sort in multiple fields and two orders + +**Pros:** More customization for user + +**Cons:** User have to type in a longer command -. Modify link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`]'s constructor to bind the `Remark` field to the `Person` 's remark. +--- -**Tests:** +**Aspect:** Initialising of `Comparator` + +**Alternative 1:** Initialise in `SortGoalCommand` + +**Pros:** Clear separation of concerns, `SortGoalCommandParser` to handle identifying of attribute to sort by only. + +**Cons:** Hard for new developers to follow as other commands like `AddGoalCommand` handles object creation in its parser. + +**Alternative 2 (current choice): ** Initialise in `UniqueGoalList` + +**Pros:** Straightforward as initialises the `Comparator` where it is used. + +**Cons:** `UniquePersonList` is at a lower level and should only handle a minimal set of `Goal` related operations, and not logical operations like string matching. + -. Modify link:{repoURL}/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysPerson(...)`] so that it will compare the now-functioning remark label. +--- +// end::sortgoal[] -===== [Step 8] Logic: Implement `RemarkCommand#execute()` logic -We now have everything set up... but we still can't modify the remarks. Let's finish it up by adding in actual logic for our `remark` command. +// tag::theme[] +=== Switching theme mechanism [Since v1.5] +CollegeZone has multiple themes for the user to choose from. Currently, there are 3 themes implemented. +Namely, `dark` theme, `bubblegum` theme and `light` theme. This command is not undoable. -**Main:** +The switch theme mechanism is facilitated by `ThemeCommandParser` and `ThemeCommand`, with both classes residing +in the `Logic` component of CollegeZone. `ThemeCommand` in not undoable. -. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a person. +`ThemeCommandParser` takes in an argument in the form of `THEME_COLOUR` that defines the theme colour of what CollegeZone +will be. You may customise the theme colour you want out of the 3 that we have. It checks for validity against a few keywords. +`THEME_COLOUR` only accepts 3 keywords: `light`, `dark` or `bubblegum`. -**Tests:** +`ThemeCommandParser` returns a new `ThemeCommand` object. `ThemeCommand` then executes the new +event change by calling `ThemeSwitchRequestEvent`. The updating of theme colour will be +done by `handleChangeThemeEvent` method. + +The following code snippet shows how the theme switch event is handled. + -. Update `RemarkCommandTest` to test that the `execute()` logic works. +[source,java] +---- +@Subscribe +private void handleChangeThemeEvent(ThemeSwitchRequestEvent event) { + themeColour = event.themeToChangeTo; + Platform.runLater( + this::changeThemeColour + ); +} +---- -==== Full Solution +==== Design Considerations -See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step-by-step solution. +**Aspect:** Storing user choice of theme + +**Alternative 1 (current choice):** User prefs will be set to `dark` theme colour by default + +**Pros:** Lesser coding required as there's no need to store theme colour as a part of the model component + +**Cons:** _CollegeZone_ does not remember user's preferred theme upon restarting _CollegeZone_ + +**Alternative 2 (current choice): ** Initialising in `UserPref` + +**Pros:** _CollegeZone_ will remember user's preferred theme upon restarting _CollegeZone_ + +**Cons:** More coding required to store theme colour -[appendix] -== Product Scope +--- +// end::theme[] -*Target user profile*: +// tag::meetCommand[] -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing over mouse input -* is reasonably comfortable using CLI apps +=== Meet Command [Since v1.2] -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +The new meet up command was implemented specifically to provide a platform in CollegeZone for RC4 students to set up +meetings with other students with ease. -[appendix] -== User Stories -Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` +*Meet Command Features:* + +The `MeetCommand` allows you to add a `MeetDate` into CollegeZone and is stored as a attribute of the `Person` class of `UniquePersonList`, in `AddressBook`. +The `DeleteMeetCommand` allows you to delete a `MeetDate` from CollegeZone. The `MeetDate` of the `Person` you deleted is set to an empty string. -[width="59%",cols="22%,<23%,<25%,<30%",options="header",] -|======================================================================= -|Priority |As a ... |I want to ... |So that I can... -|`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App +Meet commands are undoable and redoable for the benefit of RC4 Students to redo and undo a command they did or did not intend to change. + +Hence, both `MeetCommand` and `DeleteMeetCommand` are implemented as `UndoableCommand`. -|`* * *` |user |add a new person | +==== Implementation + -|`* * *` |user |delete a person |remove entries that I no longer need +*Meet Object* + -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +Every time a User sets up a meet up with someone else: + -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +1. `Meet`: This object contains a single string variable, `meetDate`, that is verified to be a valid date of the format DD/MM/YYYY. This is format is enforced to ensure user ease of usage. + +2. `Person`: The Meet Attribute that is a part of the Person attribute is then updated with the relevant `meetDate` + -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +Users can delete meet ups on the Calendar if its already due or when they accidentally made a mistake. + -_{More to be added}_ +*Adding a Meet up date* + -[appendix] -== Use Cases +When `Meet Command` is executed, it first preprocesses the data to check whether the `Person` you are meeting is a valid `Person` and also not a duplicate `Person`. If there is no `DuplicatePersonException` and `PersonNotFoundException`, then `Person` +class is updated with the `meetDate` in the `UniquePersonList`. -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +*Deleting a Meet up* + -[discrete] -=== Use case: Delete person +When `DeleteMeetCommand` is executed, it will first find the `Person` specified by the user using the `Person Index` attribute. If `Person Index` specified by user +is not found in `UniquePersonList`, `PersonNotFoundException` will be thrown. If `Person` is found, his/her "meetDate" attribute will then be removed from. The code snippet to find and remove the `Meet Date` +specified by user is shown below. -*MSS* +[source,java] +---- + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredPersonList(); -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person -+ -Use case ends. + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } -*Extensions* + personToDelete = lastShownList.get(targetIndex.getZeroBased()); + } +---- -[none] -* 2a. The list is empty. -+ -Use case ends. +*Syncing Meet ups to Calendar* + -* 3a. The given index is invalid. -+ -[none] -** 3a1. AddressBook shows an error message. -+ -Use case resumes at step 2. +To display the meet ups in the calendar, we have a `CalendarPanel` that takes in the `UniquePersonList`. -_{More to be added}_ +[source,java] +---- + public CalendarPanel(ObservableList reminderList, ObservableList personList) { + super(FXML); -[appendix] -== Non Functional Requirements + this.reminderList = reminderList; + this.personList = personList; -. Should work on any <> as long as it has Java `1.8.0_60` or higher installed. -. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. -. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. + calendarView = new CalendarView(); + setupCalendar(); + updateCalendar(); + registerAsAnEventHandler(this); + } +---- +`UniquePersonList` will then be iterated and each person with a valid meet up date in the list is individually added into the calendar through `updateCalendar()`. + Every time any `Person` is updated with a new meet up date in `CollegeZone`, an event handler, `handleNewCalendarEvent`, will +cause `calendarUpdate()` to run again and `CalendarPanel` will be updated to display the `Person` and his `meetDate`. -_{More to be added}_ +[source,java] +---- + @Subscribe + private void handleNewCalendarEvent(AddressBookChangedEvent event) { + reminderList = event.data.getReminderList(); + personList = event.data.getPersonList(); + Platform.runLater(this::updateCalendar); + } -[appendix] -== Glossary + /** + * Updates the Calendar with Reminders that are already added + */ + private void updateCalendar() { + setDateAndTime(); + CalendarSource myCalendarSource = new CalendarSource("Reminders and Meetups"); + Calendar calendarRDue = new Calendar("Reminders Already Due"); + Calendar calendarRNotDue = new Calendar("Reminders Not Due"); + Calendar calendarM = new Calendar("Meetups"); + calendarRDue.setStyle(Calendar.Style.getStyle(4)); + calendarRDue.setLookAheadDuration(Duration.ofDays(365)); + calendarRNotDue.setStyle(Calendar.Style.getStyle(1)); + calendarRNotDue.setLookAheadDuration(Duration.ofDays(365)); + calendarM.setStyle(Calendar.Style.getStyle(3)); + myCalendarSource.getCalendars().add(calendarRDue); + myCalendarSource.getCalendars().add(calendarRNotDue); + myCalendarSource.getCalendars().add(calendarM); + for (Reminder reminder : reminderList) { + LocalDateTime ldtstart = nattyDateAndTimeParser(reminder.getDateTime().toString()).get(); + LocalDateTime ldtend = nattyDateAndTimeParser(reminder.getEndDateTime().toString()).get(); + LocalDateTime now = LocalDateTime.now(); + if (now.isBefore(ldtend)) { + calendarRNotDue.addEntry(new Entry( + reminder.getReminderText().toString(), new Interval(ldtstart, ldtend))); + } else { + calendarRDue.addEntry(new Entry(reminder.getReminderText().toString(), new Interval(ldtstart, ldtend))); + } + } + //@@author sham-sheer + for (Person person : personList) { + String meetDate = person.getMeetDate().toString(); + if (!meetDate.isEmpty()) { + int day = Integer.parseInt(meetDate.substring(0, + 2)); + int month = Integer.parseInt(meetDate.substring(3, + 5)); + int year = Integer.parseInt(meetDate.substring(6, + 10)); + calendarM.addEntry(new Entry("Meeting " + person.getName().toString(), + new Interval(LocalDate.of(year, month, day), LocalTime.of(12, 0), + LocalDate.of(year, month, day), LocalTime.of(13, 0)))); + } + } + calendarView.getCalendarSources().add(myCalendarSource); + } +---- -[[mainstream-os]] Mainstream OS:: -Windows, Linux, Unix, OS-X +When a meet date is deleted, it will go through the same process as adding meet up dates and the changes will then be updated in the calendar. -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others +==== Design Considerations + +*Aspect*: Deleting a `meetDate` from `CollegeZone`. + +*Alternative 1(current choice)*: Delete `meetDate` using an index which is the index of the particular `Person` in `UniquePersonList` + +*Pros*: Implementing `DeleteReminderCommand` by parsing an index will be simple and fast. With no need for parsing of data. + +*Cons*: When your addressbook gets too large, using indexes to delete meet ups will not be a scalable option as people cant remember the individual `Indexes` relates to a `Person`. + +*Alternative 2*: Delete `meetDate` identified by `Date` or `Person`. + +*Pros*: Reduces the need of a listing and finding function to delete a `meetDate` from `CollegeZone`. + +*Cons*: Implementation of `DeleteReminderCommand` will be more difficult as we will have to integrate a find function to pick out +the specific `meetDates` or 'Person' that the user wants to remove. + +// end::meetCommand[] + +//tag::sortmech[] + +=== Sort Command [Since v1.4] + +The Sort Command is facilitated by `SortCommandParser` and `SortCommand`, with both classes residing in the `Logic` component of the address book. Since the address book state will be modified during the sorting process, the `sort` has to be undoable. + +`SortCommandParser` takes in an argument in the form of `INDEX_TYPE` that defines how `UniquePersonList` should be sorted. You may customise the sort operation, with `PREFIX` specifying the sort type. It first checks for validity against a regular expression. Once verified, the argument will be tokenized to identify your specified sort type. A `SortCommand` object is then created with the identified sort type. + +The `INDEX_TYPE` can be any three of the following: `1` for sorting RC4 Students based on their level of friendship, `2` for sorting persons by meet date, `3` for sorting persons by Birthday. The sorted list is always default to descending order of importance. + +Upon execution of `SortCommand`, a `Comparator` will be initialised based on the sort type it receives. A `sortPersons` function call will be made to `Model`, which propagates down to `UniquePersonList`, where the sorting of the `internalList` occurs. Since sorting of `internalList` results in the change of state to address book, `SortCommand` is to be implemented as an `UndoableCommand`. + +image::LogicCommandClassDiagram_Sort.png[width="800"] +_Figure 4.5.1 : Structure of Sort Command in the Logic Component_ + +[NOTE] +Implementation of the Sort Command requires both the manipulation of `Logic` and `Model` component of address book. + +The following sequence diagram shows the flow of operation from the point the address book receives an input to the output of the result. + +.Interactions Inside the Logic Component for the `sort 1` Command_ +image::SortPersonSdForLogic.png[width="800"] + +[NOTE] +If the list is found to be empty, an `CommandException` will be thrown from `SortCommand`. The command should be terminated without any state change, keeping the redoStack clean of changes. + +==== Design Considerations + +**Aspect:** Initialising of `Comparator` + +**Alternative 1:** Initialise in `SortCommand` + +**Pros:** Clear separation of concerns, `SortCommandParser` to handle identifying of attribute to sort by only. + +**Cons:** Hard for new developers to follow as other commands like `AddCommand` handles object creation in its parser. + +**Alternative 2 (current choice): ** Initialise in `UniquePersonList` + +**Pros:** Straightforward as initialises the `Comparator` where it is used. + +**Cons:** `UniquePersonList` is at a lower level and should only handle a minimal set of `Person` related operations, and not logical operations like string matching. + + +--- + +**Aspect:** Sorting by multiple attribute + +**Alternative 1 (current choice):** Only allows sorting by single attribute + +**Pros:** Fast and arguments to input is straightforward. + +**Cons:** Unable to have fine grain control of how list should appear. + +**Alternative 2:** Allow sorting by multiple attribute + +**Pros:** Enables fine grain control of how list should appear. + +**Cons:** Not necessary as effect is only obvious when contact list is long and has multiple common names. As target audience for iConnect are students, contact list will not be more than few thousand contacts long. + + +// end::sortmech[] + + +// tag::rate[] +=== Rate friends [Since v1.4] +The Rate friends feature allows RC4 residents to rate their friends and change their levels of friendship. +This feature is implemented by the `RateCommand` and `RateCommandParser` in the Logic component of the CollegeZone code. +The RC4 student is able to rate one or more friends by keying in the new desired level of friendship through the Command Line Interface (CLI). +The `RateCommand` inherits from `UndoableCommands` as well, as shown in the diagram below. + +image::RateCommandClassDiagram.jpg[width="400"] + +To rate other RC4 residents and friends, the `LevelOfFriendship` class is being used and is part of the `Person` class. +A `Person` is composed of a `LevelOfFriendship` component, and each person in CollegeZone application has a particular level of friendship between `1` to `10`. +The next diagram illustrates the relationship between a +`Person` and its `LevelOfFriendship`. + + +image::RC4ModelComponenetClass.JPG[width="800"] + + +The following shows a part of the code of `RateCommand` and reveals +the parameters that `RateCommand` makes use of. + +[source,java] + public RateCommand(List indexList, String levelOfFriendship) { + } + +As observed, `RateCommand` involves two parameters, namely `indexList` and `leveloffriendship`. + +`indexList` has a `List` of indexes type, and `leveloffriendship` is of `String` type. + +The parameter `indexList` refers to the list of students whose are intended to be rated, and thus +`RateCommand` is able to help RC4 residents rate multiple people at a time. The `leveloffriendship` +parameter refers to the new level of friendship that the resident would like to rate their friends to. + +The following code sample shows the execution of `RateCommnad`, +[source,java] +public CommandResult executeUndoableCommand() throws CommandException { + List latestList = model.getFilteredPersonList(); + for (Index index : indexList) { + Person selectedPerson = latestList.get(index.getZeroBased()); + try { + Person editedPerson = new Person(selectedPerson.getName(), selectedPerson.getPhone(), + selectedPerson.getBirthday(), new LevelOfFriendship(levelOfFriendship), + selectedPerson.getUnitNumber(), + selectedPerson.getCcas(), selectedPerson.getMeetDate(), selectedPerson.getTags()); + model.updatePerson(selectedPerson, editedPerson); + +As seen, the index of the student whose level of friendship is to be rated and changed, a new `editedPerson` object is created +and all the details of the person, the name and phone number and other details were copied from the `selectedPerson` and is assigned the new level of friendship from the rate command. + + +[NOTE] +If an invalid index value is entered, i.e the person with an `index` of which does not exist in CollegeZone contact list is entered with valid `index` entries, +only the valid entries will have their `Level of friendships` rated and updated. +As seen in the code below, there will be a error message informing the user that they have keyed in an invalid `index` value. + +[source,java] + if (index.getZeroBased() >= latestList.size()) { + throw new CommandException(MESSAGE_ONE_OR_MORE_INVALID_INDEX); + } + + +==== Design Considerations + +**Aspect:** Implementation of `RateCommand`. + +**Alternative 1 (current choice):** Creates a new `Person` object which copies all its respective personal details and adds a new `LevelOfFriendship` value. + +**Pros:** It uses a pre-existing method, and additional methods to implement `RateCommand` need not be created and added. + +**Cons:** Copying all respective personal data in order to change only the `LevelOfFriendship` attribute can be excessive as cause additional processing time if a `person` have many attributes. + +**Alternative 2:** Add a `changeLevelOfFriendship` setter method in `Person` class + +**Pros:** Relatively simple to implement. + +**Cons:** Additional methods have to be added to ensure that the `input values` and `indexes` are valid. + +// end::rate[] + + +== Documentation + +We use asciidoc for writing documentation. + +[NOTE] +We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting. + +=== Editing Documentation + +See <> to learn how to render `.adoc` files locally to preview the end result of your edits. +Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your `.adoc` files in real-time. + +=== Publishing Documentation + +See <> to learn how to deploy GitHub Pages using Travis. + +=== Converting Documentation to PDF format + +We use https://www.google.com/chrome/browser/desktop/[Google Chrome] for converting documentation to PDF format, as Chrome's PDF engine preserves hyperlinks used in webpages. + +Here are the steps to convert the project documentation files to PDF format. + +. Follow the instructions in <> to convert the AsciiDoc files in the `docs/` directory to HTML format. +. Go to your generated HTML files in the `build/docs` folder, right click on them and select `Open with` -> `Google Chrome`. +. Within Chrome, click on the `Print` option in Chrome's menu. +. Set the destination to `Save as PDF`, then click `Save` to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below. + +.Saving documentation as PDF files in Chrome +image::chrome_save_as_pdf.png[width="300"] + +[[Testing]] +== Testing + +=== Running Tests + +There are three ways to run tests. + +[TIP] +The most reliable way to run tests is the 3rd one. The first two methods might fail some GUI tests due to platform/resolution-specific idiosyncrasies. + +*Method 1: Using IntelliJ JUnit test runner* + +* To run all tests, right-click on the `src/test/java` folder and choose `Run 'All Tests'` +* To run a subset of tests, you can right-click on a test package, test class, or a test and choose `Run 'ABC'` + +*Method 2: Using Gradle* + +* Open a console and run the command `gradlew clean allTests` (Mac/Linux: `./gradlew clean allTests`) + +[NOTE] +See <> for more info on how to run tests using Gradle. + +*Method 3: Using Gradle (headless)* + +Thanks to the https://github.com/TestFX/TestFX[TestFX] library we use, our GUI tests can be run in the _headless_ mode. In the headless mode, GUI tests do not show up on the screen. That means the developer can do other things on the Computer while the tests are running. + +To run tests in headless mode, open a console and run the command `gradlew clean headless allTests` (Mac/Linux: `./gradlew clean headless allTests`) + +=== Types of tests + +We have two types of tests: + +. *GUI Tests* - These are tests involving the GUI. They include, +.. _System Tests_ that test the entire App by simulating user actions on the GUI. These are in the `systemtests` package. +.. _Unit tests_ that test the individual components. These are in `seedu.address.ui` package. +. *Non-GUI Tests* - These are tests not involving the GUI. They include, +.. _Unit tests_ targeting the lowest level methods/classes. + +e.g. `seedu.address.commons.StringUtilTest` +.. _Integration tests_ that are checking the integration of multiple code units (those code units are assumed to be working). + +e.g. `seedu.address.storage.StorageManagerTest` +.. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together. + +e.g. `seedu.address.logic.LogicManagerTest` + + +=== Troubleshooting Testing +**Problem: `HelpWindowTest` fails with a `NullPointerException`.** + +* Reason: One of its dependencies, `UserGuide.html` in `src/main/resources/docs` is missing. +* Solution: Execute Gradle task `processResources`. + +== Dev Ops + +=== Build Automation + +See <> to learn how to use Gradle for build automation. + +=== Continuous Integration + +We use https://travis-ci.org/[Travis CI] and https://www.appveyor.com/[AppVeyor] to perform _Continuous Integration_ on our projects. See <> and <> for more details. + +=== Coverage Reporting + +We use https://coveralls.io/[Coveralls] to track the code coverage of our projects. See <> for more details. + +=== Documentation Previews +When a pull request has changes to asciidoc files, you can use https://www.netlify.com/[Netlify] to see a preview of how the HTML version of those asciidoc files will look like when the pull request is merged. See <> for more details. + +=== Making a Release + +Here are the steps to create a new release. + +. Update the version number in link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp.java`]. +. Generate a JAR file <>. +. Tag the repo with the version number. e.g. `v0.1` +. https://help.github.com/articles/creating-releases/[Create a new release using GitHub] and upload the JAR file you created. + +=== Managing Dependencies + +A project often depends on third-party libraries. For example, CollegeZone depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + +a. Include those libraries in the repo (this bloats the repo size) + +b. Require developers to download those libraries manually (this creates extra work for developers) + +[[GetStartedProgramming]] +[appendix] +== Product Scope + +*Target user profile*: + +* Current NUS Students living in Residential College 4 (RC4) +* has a need to manage a significant number of contacts (friends) and tasks to do +* has a need to be reminded of things to do +* has a need to keep track of goals that they have +* prefer desktop apps over other types +* can type fast +* prefers typing over mouse input +* is reasonably comfortable using CLI apps + +*Value proposition*: manage contacts and tasks faster than a typical mouse/GUI driven app + +*Feature Contribution*: +[width="90%",cols="20%,<25%,<25%",options="header",] +|======================================================================= +|Assignee |Major |Minor + +|Deborah Low +|Goals Panel : Allows user to set/edit/delete goals they have for the year and to keep track of their goals progress. + + Allows user to indicate goal is still ongoing or has already been completed. + Allows user to sort goals. +|Add and Edit : Change add and edit command to suit our target audience ( RC4 Students ) - adding birthday, cca, level of friendship and unit number field for student. + + GUI : Change the look and feel of the GUI to make it more user friendly. Allows user to switch themes. + +|Fuad B Sahmawi +|Calendar: Integrate CalendarFX onto CollegeZone UI + + Reminder: Allows user to set/delete reminders reflected on the Calendar. Due reminders are marked red while undue reminders marked blue. +|Find : Change find command to be able to find persons in contact list according to tags + + Logic : Added command aliases to allow users to be able to perform commands by typing shortcuts + +|Shamsheer Ahamed +|Social (Meet-Up) : This feature allows user to set up meet ups with RC4 students that will be reflected on a Calendar + + Social (Sorting) : On top of the meet up dates appearing on the calendar, a sorting tool is also added to keep the user up to date with his meet up dates, birthdays and friendship levels. +|Command Box Enhancement : Added a autocomplete command that auto fills the required preambles for the individual commands in the command box + +|Goh Zu Wei +|Rate Friends : This feature allows categorize and rate one or more friends by changing their levels of friendships. +|Seek: Add seek command to be able to seek the Resident Assistant (RA) of any particular the student living in RC4 + +|======================================================================= + +[appendix] +== User Stories + +Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` + +[width="59%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= +|Priority |As a ... |I want to ... |So that I can... +|`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App + +|`* * *` |RC student |add a new person | + +|`* * *` |RC student |delete a person |remove entries that I no longer need + +|`* * *` |RC student |find a person by name |locate details of persons without having to go through the entire list + +|`* * *` |RC student |find a person by tags |locate a particular group of people without having to go through the entire list + +|`* * *` |RC student |edit a detail I added | + +|`* * *` |RC student |add my goals for the year |keep track of the goals I have and have not completed + +|`* * *` |RC student |set a level of friendship with a specific person |maintain my friendships depending on a priority system set by myself + +|`* * *` |RC student |Rate my friends |keep track and update of who my close friends are + +|`* * *` |RC student |edit details of my contacts |stay updated with the current information about my contacts + +|`* * *` |forgetful RC student |add persistent reminders |periodically remind myself to do something. + +|`* * *` |forgetful RC student |add other RC friends name, birthday, hall CCAs and tags into CollegeZone | + +|`* * *` |forgetful RC student |set up a meet up with another RC4 student |shows who you are meeting up with on the calendar + +|`* * *` |RC student |note down tasks, events or training sessions in a calendar |make my schedule more organised + +|`* * *` |RC student |Set down a date for group events |do necessary group preparation prior to a group event + +|`* * *` |RC student |Set up meetings and keep track of them |I can effectively network and meet new people in my RC + +|`* * *` |RC student |easily find out important dates like meeting dates and birth dates |be up to task with those dates + +|`* *` |careless RC student |undo a command I entered |undo a wrong command that I entered + +|`* *` |careless RC student |redo a command I entered |redo when I want to undo my "undo" command + +|`* *` |RC student |write down a short reflection of how an event/training session went |remember precious moments easier in the future + +|`* *` |RC student |list down all past appointments with a particular friend |reminisce past memories with a particular friend + +|`* *` |RC student |hide <> by default |minimize chance of someone else seeing them by accident + +|`* *` |RC student | be reminded on when my campus fees are due | pay it on time + +|`* *` |RC student |know who the Resident Assistant (RA) is of a fellow resident |find the RA of the resident and convey floor issues to the RA + +|`*` |user with many persons in CollegeZone |sort persons by name |locate a person easily + +|`*` |user with many persons with the same in CollegeZone |set a display picture of each contact |differentiate persons with the same name + +|======================================================================= + +_{More to be added}_ + +[appendix] +== Use Cases + +=== Use case: Add student + +*MSS* + +1. User requests to add a student to the list +2. CollegeZone adds the student ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The given detail format is invalid. ++ +[none] +** 1a1. CollegeZone shows an error message. ++ +Use case ends. + +=== Use case: List students +1. User requests to list students +2. CollegeZone shows a list of students ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +=== Use case: Delete student + +*MSS* + +1. User requests to list students +2. CollegeZone shows a list of students +3. User requests to delete a specific student in the list +4. CollegeZone deletes the student ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given index is invalid. ++ +[none] +** 3a1. CollegeZone shows an error message. ++ +Use case resumes at step 2. + +=== Use case: Edit student +1. User requests to list students +2. CollegeZone shows a list of students +3. User requests to edit a detail or multiple details of a student in the list +4. CollegeZone edits the detail or details of the student ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given index is invalid. ++ +[none] +** 3a1. CollegeZone shows an error message. ++ + +* 3b. The given detail format is invalid. ++ +[none] +** 3b1. CollegeZone shows an error message. ++ +Use case resumes at step 2. + +=== Use case: Find student +1. User requests to find student by tag or name using keywords +2. CollegeZone shows a list of students ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The given detail format is invalid. ++ +[none] +** 1a1. CollegeZone shows an error message + +[none] +* 2a. The list has all students with name or tag that matches keywords ++ +Use case ends. + +[none] +* 2b. The list is empty ++ +Use case ends. + +=== Use case: Select student or goal +1. User requests to list students +2. CollegeZone shows a list of students +3. User requests to select a student or goal +4. CollegeZone shows the detail of the student or goal ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +[none] +* 3a. The given INDEX for either student or goal is invalid. ++ +[none] +** 3a1. CollegeZone shows an error message ++ +Use case ends. + +=== Use case: Add reminder + +1. User requests to add a reminder on a certain date +2. CollegeZone adds the reminder in the calendar and changes are reflected on the calendar ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The given date detail in invalid. ++ +[none] +** 1a1. CollegeZone shows an error message. ++ +Use case ends. + + +=== Use case: Delete reminder + + +1. User requests to delete a certain reminder on a certain date +2. CollegeZone delete the reminder from the calendar and changes is reflected on the calendar ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The given reminder to delete does not exist. ++ +[none] +** 1a1. CollegeZone shows an error message. ++ +Use case ends. + +* 1b. The given details to delete reminder is invalid. ++ +[none] +** 1b1. CollegeZone shows an error message. ++ +Use case ends. + + + +=== Use case: Meet student + +1. User request to add a meet up date on a certain date with a student using his index +2. CollegeZone adds the meet up in the calendar and changes are reflected in the calendar ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The given date is invalid. ++ +[none] +** 1a1. CollegeZone shows an error message. ++ +Use case ends. + +* 1b. The given student's index is invalid. ++ +[none] +** 1b1. CollegeZone shows an error message. ++ +Use case ends. + +=== Use case: Sort students + +1. Users requests to sort the RC4 Students list according to meet up dates. +2. CollegeZone sorts the list and shows the upcoming the meet dates first. ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The list is empty ++ +[none] +** 1a1. CollegeZone shows an error message. ++ +Use case ends. + +* 1b. The given sorting index type is invalid. ++ +[none] +** 1b1. CollegeZone shows an error message. ++ +Use case ends. + +=== Use case: Add goal +1. User requests to add a goal in the list +2. CollegeZone adds the goal ++ +Use case ends. + +*Extensions* + +* 1a. The given goal details is invalid. ++ +[none] +** 1a1. CollegeZone shows an error message. ++ +Use case ends. + + +=== Use case: Edit goal +1. CollegeZone shows a list of goals +2. User requests to edit a detail or multiple details of a goal in the list +3. CollegeZone edits the detail or details of the selected goal ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The list is empty. ++ +Use case ends. + +* 2a. The given index is invalid. ++ +[none] +** 2a1. CollegeZone shows an error message. ++ +Use case ends. + +* 2b. The given goal detail format is invalid. ++ +[none] +** 2b1. CollegeZone shows an error message. ++ +Use case ends. + +* 2c. The given goal details is invalid. ++ +[none] +** 2c1. CollegeZone shows an error message. ++ +Use case ends. + +=== Use case: Delete goal + +*MSS* + +1. CollegeZone shows a list of goals +2. User requests to delete a specific goal in the list +3. CollegeZone deletes the goal ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The list is empty. ++ +Use case ends. + +* 2a. The given index is invalid. ++ +[none] +** 2a1. CollegeZone shows an error message. ++ +Use case ends. + +=== Use case: Complete goal + +*MSS* + +1. CollegeZone shows a list of goals +2. User requests to complete a specific goal in the list +3. CollegeZone indicates the specified goal is completed ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The list is empty. ++ +[none] +** 1a1. CollegeZone shows an error message. + +* 2a. The given index is invalid. ++ +[none] +** 2a1. CollegeZone shows an error message. ++ +Use case ends. + +* 2b. The specified goal is already completed. ++ +[none] +** 2b1. CollegeZone shows an error message. ++ +Use case ends. + +=== Use case: Ongoing goal + +*MSS* + +1. CollegeZone shows a list of goals +2. User requests to indicate goal is ongoing to a specific goal in the list +3. CollegeZone indicates the specified goal is ongoing ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The list is empty. ++ +[none] +** 1a1. CollegeZone shows an error message. ++ +Use case ends. + +* 2a. The given index is invalid. ++ +[none] +** 2a1. CollegeZone shows an error message. ++ +Use case ends. + +* 2b. The specified goal is already ongoing. ++ +[none] +** 2b1. CollegeZone shows an error message. ++ +Use case ends. + +=== Use case: Sort goal + +*MSS* + +1. CollegeZone shows a list of goals +2. User requests sort goal based on field and order to sort +3. CollegeZone sort the goal list based on field and order specified ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The list is empty. ++ +[none] +** 1a1. CollegeZone shows an error message. ++ +Use case ends. + +* 2a. The given format is invalid. ++ +[none] +** 2a1. CollegeZone shows an error message. ++ +Use case ends. + +* 2b. The given field is invalid. ++ +[none] +** 2b1. CollegeZone shows an error message. ++ +Use case ends. + +* 2c. The given order is invalid. ++ +[none] +** 2c1. CollegeZone shows an error message. ++ +Use case ends. + +=== Use case: Seek Resident Assistant (RA) + +*MSS* + +1. User requests to find students' RA by name using keywords. +2. CollegeZone shows a list of students and Resident assistants (RA). ++ +Use case ends. + +*Extensions* +[none] +* 1a. The given detail format is invalid. ++ +[none] +** 1a1. CollegeZone shows an error message. + +[none] +* 2a. The list has all students and RA(s) with name that matches keywords. ++ +Use case ends. + +[none] +* 2b. The list is empty ++ +Use case ends. + +=== Use case: Rate friends +*MSS* + +1. User requests to list or show students of a particular level of friendship. +2. CollegeZone shows a list of students. +3. User requests to rate one or more student in the list. +4. CollegeZone rates and changes the level of friendship of the student(s). ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. ++ +Use case ends. + +* 3a. The given index is invalid. ++ +[none] +** 3a1. CollegeZone shows an error message. ++ + +* 3b. The given detail format is invalid. ++ +[none] +** 3b1. CollegeZone shows an error message. ++ +Use case ends. + +=== Use case: Show student +*MSS* + +1. User requests to show student by level of friendship using valid value. +2. CollegeZone shows a list of students of a particular level of friendship. ++ +Use case ends. + +*Extensions* +[none] + +* 1a. The given detail format is invalid. ++ +[none] +** 1a1. CollegeZone shows an error message. + +[none] +* 2a. The list has all students with level of friendship that matches input value. ++ +Use case ends. + +[none] +* 2b. The list is empty. ++ +Use case ends. + +=== Use case: Switch theme colour + +*MSS* + +1. CollegeZone has a theme colour +2. User requests to switch theme colour +3. CollegeZone switches theme colour ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The given theme colour is invalid. ++ +[none] +** 2a1. CollegeZone shows an error message. ++ +Use case ends. + +* 2b. The given theme colour is currently in use. ++ +Use case ends. + +=== Use case: Undo + +*MSS* + +1. User requests to undo a command +2. CollegeZone undo the command and update _CollegeZone_ ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The given format is invalid. ++ +[none] +** 1a1. CollegeZone shows an error message. ++ +Use case ends. + +* 1b. There are no more commands to undo. ++ +[none] +** 1b1. CollegeZone shows an error message. ++ +Use case ends. + +=== Use case: Redo + +*MSS* + +1. User requests to redo a command +2. CollegeZone redo the command and update _CollegeZone_ ++ +Use case ends. + +*Extensions* + +[none] +* 1a. The given format is invalid. ++ +[none] +** 1a1. CollegeZone shows an error message. ++ +Use case ends. + +* 1b. There are no more commands to redo. ++ +[none] +** 1b1. CollegeZone shows an error message. ++ +Use case ends. + +=== Use case: Seek Resident Assistant (RA) + +=== Use case: History + +*MSS* + +1. User requests to toggle command history +2. CollegeZone displays command history ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The given format is invalid. ++ +[none] +** 2a1. CollegeZone shows an error message. ++ +Use case ends. + +=== Use case: Clear + +*MSS* + +1. User requests to clear CollegeZone +2. CollegeZone deletes all data ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The given format is invalid. ++ +[none] +** 2a1. CollegeZone shows an error message. ++ +Use case ends. + +=== Use case: Help + +*MSS* + +1. User requests for help page in CollegeZone +2. CollegeZone opens help page ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The given format is invalid. ++ +[none] +** 2a1. CollegeZone shows an error message. ++ +Use case ends. + +=== Use case: Exit CollegeZone + +*MSS* + +1. User requests to exit CollegeZone +2. CollegeZone displays command history ++ +Use case ends. + +*Extensions* + +[none] +* 2a. The given format is invalid. ++ +[none] +** 2a1. CollegeZone shows an error message. ++ +Use case ends. + +[appendix] +== Non Functional Requirements + +. Should work on any <> as long as it has Java `1.8.0_60` or higher installed. +. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. +. Should be intuitive to use for users who are not tech-savvy. +. Should be able to be accessed offline. +. The system should respond within 2 seconds. +. Should work on 32-bit and 64-bit environment. +. Should store data locally and should be in a .xml file. +_{More to be added}_ + +[appendix] +== Glossary + +[[mainstream-os]] Mainstream OS:: +Windows, Linux, Unix, OS-X + +[[private-contact-detail]] Private contact detail:: +A contact detail that is not meant to be shared with others + +[[CCA]] Co-Curricular Activities:: +Co-Curricular Activities offered within Residential College 4 (RC4) + +[[RC4]] Residential College 4:: +A campus living area at NUS U-Town for NUS undergraduate students [appendix] == Product Survey @@ -905,15 +2263,15 @@ These instructions only provide a starting point for testers to work on; testers _{ more test cases ... }_ -=== Deleting a person +=== Deleting a student -. Deleting a person while all persons are listed +. Deleting a student while all students are listed -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +.. Prerequisites: List all students using the `list` command. Multiple students in the list. .. Test case: `delete 1` + Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. .. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + Expected: No student is deleted. Error details shown in the status message. Status bar remains the same. .. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + Expected: Similar to previous. diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 74248917e438..74742b8a26bb 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - User Guide += CollegeZone - User Guide :toc: :toc-title: :toc-placement: preamble @@ -11,36 +11,67 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4 +:repoURL: https://github.com/CS2103JAN2018-T09-B2/main -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `Team T09-B2` Since: `March 2018` Licence: `MIT` == Introduction +Welcome to *_CollegeZone_*! + + + +_CollegeZone_ is a revolutionary desktop application created for National University of Singapore (NUS) students living in Residential College 4 (RC4). We aim to help you to manage your hectic University life! + +_CollegeZone_ is a personal assistant to your everyday life in NUS. It helps you to maintain and expand your social circle, tells you the goals you have, and schedules reminders and appointments you have. +More importantly, _CollegeZone_ is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, _CollegeZone_ can get your contact management and tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! == Quick Start +Follow these few simple steps to get started with _CollegeZone_! + +=== Download . Ensure you have Java version `1.8.0_60` or later installed in your Computer. + [NOTE] Having any Java 8 version is not enough. + This app will not work with earlier versions of Java 8. + -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. -. Double-click the file to start the app. The GUI should appear in a few seconds. +. Download the latest `collegezone.jar` link:{repoURL}/releases[here]. ++ +image::DownloadJar.JPG[width="550"] + -image::Ui.png[width="790"] + +. Copy the file to the folder you want to use as the home folder for _CollegeZone_. For example, you can place it in a folder created on your desktop. + +=== Launch +. To launch _CollegeZone_, double-click on `collegezone.jar` to launch _CollegeZone_. +. You will be greeted with _CollegeZone's_ Graphic User Interface (GUI) as shown below. The GUI should appear in a few seconds. + -. Type the command in the command box and press kbd:[Enter] to execute it. + +image::Ui.png[width="800"] + + +=== Visual Introduction + +The image below describes what each part of _CollegeZone's_ GUI represent. + +image::UserGuideLabelledUI.PNG[width="800"] + +. The *Command Box* is located at the top of CollegeZne. Enter your keyboard commands into the box! +. The *Result Display Panel* shows you the results of the commands you entered. +. The *Student List* keeps the list of student contacts that you have. +. The *Goal List* keeps the list of goals that you have. +. The *Goal Percentage Bar* is a percentage counter of the goals you have completed. +. The *Calendar* keeps the schedules that you have. + +=== Start using CollegeZone +Now that you have _CollegeZone's_ application on, try out some of the commands that we offer! + +* Type the command in the command box and press kbd:[Enter] to execute it. + e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. -. Some example commands you can try: +* Some example commands you can try: -* *`list`* : lists all contacts -* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the Address Book. -* **`delete`**`3` : deletes the 3rd contact shown in the current list -* *`exit`* : exits the app +** *`list`* : lists all RC4 student contacts +** **`add`**`n/John Doe p/98765432 */9 b/21 May 1997 u/#10-12 cca/Badminton cca/Volleyball t/workout buddy` : adds a student contact named `John Doe` to _CollegeZone_. +** **`delete`**`3` : deletes the 3rd student description shown in the student list +** *`exit`* : exits _CollegeZone_ . Refer to <> for details of each command. @@ -50,118 +81,717 @@ e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. ==== *Command Format* +* Words enclosed in round brackets indicates the *Command keyword* to use e.g. in `(add)`, `add` is the command key word to use. +* *Command keywords* separated by kbd:[|] executes the same command e.g. in `(add | a)`, `add` or `a` are equivalent command key words to use. * Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. -* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. +* Items in square brackets are optional e.g. `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. * Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. * Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +* There are no limit to the number of tags or ccas a student can have. +[TIP] +Sentences displayed in this manner indicates a TIP for you +[NOTE] +Sentences displayed in this manner indicates a NOTE to look out for + ==== +=== General features +[discrete] === Viewing help : `help` +First, let's get familiar with the command features that _CollegeZone_ offers! Type `help` in the Command Box and press kbd:[Enter] to display all the possible command usage. -Format: `help` +*_Format:_* +```java +(help) +``` -=== Adding a person: `add` +[discrete] +=== Adding a student: `add` or `a` +Currently, _CollegeZone_ is empty. Try to add a fellow RC4 friend into _CollegeZone_ using the `add` or `a` command. -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +*_Formats:_* +```java +(add | a) n/NAME p/PHONE_NUMBER */LEVEL_OF_FRIENDSHIP b/BIRTHDAY u/UNIT_NUMBER [cca/CCA]... [t/TAG]... +``` + +[NOTE] +==== +`LEVEL_OF_FRIENDSHIP` must be a positive integer ranging from 1 to 10. +==== +[NOTE] +==== +`BIRTHDAY` must be a in DDMMYYYY format . +==== [TIP] -A person can have any number of tags (including 0) +A student can have any number of tags (including 0) + +A student can have any number of CCAs (including 0) -Examples: +*_Examples_:* +```java +> add n/John Doe p/98765432 */9 b/21-May-1997 u/#10-12 cca/Badminton cca/Volleyball t/workout buddy +``` +```java +> a n/Betsy Crowe t/friend b/21/12/1994 u/#01-10 p/1234567 */1 t/OwesMoney +``` + +[discrete] +=== Listing all students : `list` or `l` -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +Shows a list of all students and their details that you added into _CollegeZone_. + -=== Listing all persons : `list` +*_Formats:_* +```java +(list | l) +``` -Shows a list of all persons in the address book. + -Format: `list` +[discrete] +=== Editing a student : `edit` or `e` [Since v1.2] -=== Editing a person : `edit` +If you added a student detail incorrectly, you can edit an existing student's details in _CollegeZone_. + -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +*_Formats:_* +```java +(edit | e) INDEX [n/NAME] [p/PHONE] [u/UNIT_NUMBER] [*/LEVEL_OF_FRIENDSHIP] [b/BIRTHDAY] [cca/CCA]... [t/TAG]... +``` **** -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the last person listing. The index *must be a positive integer* 1, 2, 3, ... +* Edits the student at the specified `INDEX`. The index refers to the index number shown in the last student listing. The index *must be a positive integer* 1, 2, 3, ... * At least one of the optional fields must be provided. * Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person's tags by typing `t/` without specifying any tags after it. +* When editing tags or CCAs, the existing tags or CCAs of the student will be removed i.e adding of tags or CCAs is not cumulative. +* You can remove all the student's tags by typing `t/` without specifying any tags after it. **** -Examples: +*_Examples_:* +```java +> edit 1 p/91234567 */10 +``` +Edits the phone number and level of friendship of the 1st student to be `91234567` and `10` respectively. + +```java +> e 2 n/Betsy Crower t/ +``` +Edits the name of the 2nd student to be `Betsy Crower` and clears all existing tags. -* `edit 1 p/91234567 e/johndoe@example.com` + -Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` + -Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +[discrete] +=== Locating students by name or tag: `find` or `f` [Since v1.1] -=== Locating persons by name: `find` +_CollegeZone_ lets you find students whose names or tags contain any of the given keywords. + -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +*_Formats:_* +```java + (find | f) [n/KEYWORDS] +``` +```java + (find | f) [t/KEYWORDS] +``` **** * The search is case insensitive. e.g `hans` will match `Hans` * The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. +* Only the name or tag is searched at a single time * Only full words will be matched e.g. `Han` will not match `Hans` * Persons matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +* Searching both name and tag at the same time is not possible **** -Examples: - -* `find John` + +*_Examples_:* +```java +> find n/John +``` Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +```java +> f n/Betsy Tim John +``` +Returns any student having names `Betsy`, `Tim`, or `John` +```java +> find t/friends +``` +Returns any student having tags `friends` + +// tag::seek[] +[discrete] +=== Seeking the Resident Assistant(s) of a student : `seek` [Since v1.3] +Seek the Resident Assistant(s) (RA) of the student(s) whose name contains any of the given keywords. + +*_Formats:_* +```java +seek NAME +``` -=== Deleting a person : `delete` +**** +* The search is case insensitive. e.g `hans` will match `Hans` +* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` +* Only the name is searched at a single time +* Only full words will be matched e.g. `Han` will not match `Hans` +* Resident Assistant(s) (RA) of the student's name matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`, `RA(s) of RC4`. +**** + +*_Examples_:* +```java +> seek John +``` +Returns `John` and `RA(s) of RC4` +```java +> seek Betsy +``` +Returns `Betsy` and `RA(s) of RC4` +// end::seek[] + +[discrete] +=== Deleting a student : `delete` or `d` + +If you want to remove a student from your list, you're able to delete the specified student data from _CollegeZone_. + -Deletes the specified person from the address book. + -Format: `delete INDEX` +*_Formats:_* +```java +(delete | d) INDEX +``` **** -* Deletes the person at the specified `INDEX`. +* Deletes the student at the specified `INDEX`. * The index refers to the index number shown in the most recent listing. * The index *must be a positive integer* 1, 2, 3, ... **** -Examples: +*_Examples_:* +```java +> list +> delete 2 +``` +Deletes the 2nd student in _CollegeZone_. +```java +> find Betsy +> d 1 +``` +Deletes the 1st student in the results of the `find` command. -* `list` + -`delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + -`delete 1` + -Deletes the 1st person in the results of the `find` command. +[discrete] +=== Selecting a student and goal: `select` or `s` -=== Selecting a person : `select` +Selects the student and/or goal identified by the index number used in the last student/goal listing. + -Selects the person identified by the index number used in the last person listing. + -Format: `select INDEX` +*_Formats:_* +```java +(select | s) INDEX +``` **** -* Selects the person and loads the Google search page the person at the specified `INDEX`. +* Selects the student or goal at the specified `INDEX`. * The index refers to the index number shown in the most recent listing. * The index *must be a positive integer* `1, 2, 3, ...` **** -Examples: +*_Examples_:* +```java +> list +> select 2 +``` +Selects the 2nd student and 2nd goal in _CollegeZone_. +```java +> find Betsy +> s 1 +``` +Selects the 1st student in the results of the `find` command. + + +//tag::meet[] + +==== Meeting a student : `meet` or `m` [Since v1.3] + + +Arranges a meetup with the student identified by the index number used in the last student listing. + +*_Formats:_* +```java +(meet| m) INDEX d/MEETDATE +``` + +**** +* Sets up a meeting with the student at the specified `INDEX` on the specified meet date. +* The index refers to the index number shown in the most recent listing. +* The index *must be a positive integer* `1, 2, 3, ...` +* The date must be of the format d/ + DD/MM/YYYY. +**** + +*_Examples_:* +```java +> meet 1 d/14/03/2018 +``` +Sets up a meeting with the 1st student on the 14th of March, 2018 in your Calendar. +//end::meet[] + +// tag::rate[] +[discrete] +=== Rating feature : `rate` or `rt` [Since v1.4] + +Rates an existing student in _CollegeZone_. + + +*_Formats:_* +```java +(rate | rt) INDEX(s) [*/LEVEL_OF_FRIENDSHIP] +``` + +**** +* Rates the student at the specified `INDEX(s)`. The index(s) refers to the index number shown in the last student listing. The index *must be a positive integer* 1, 2, 3, ... +* At least one valid index and a valid level of friendship rating must be provided. +* Existing values will be updated to the input values. +* When rating students, the current existing level of friendship value of the student will be changed. +**** + +*_Examples_:* +```java +> rate 1 */5 +``` +Rates the level of friendship of the 1st student to be `5`. + +```java +> rt 1 3 */7 +``` +Rates the level of friendship of the 1st and 3rd student to be `5`. + +[discrete] +=== Show level of friendship feature : `show` or `sh` [Since v1.5] + +Shows the level of friendship of the student(s) whose level of friendship contains any of the given input values. + + +*_Formats:_* +```java +(show|sh) LEVELOFFRIENDSHIP +``` + +**** +* The order of the input level of friendship values does not matter. e.g. `1 2` will match `1` and `2` +* Only valid level of friendship values will be matched e.g. `11` will not match `1` +* Level of frienship of the student matching at least one value will be returned (i.e. `OR` search). e.g. `1 2` will return `Students with Level of Friendship value 1` and `Students with Level of Friendship value 2`. +**** + +*_Examples_:* +```java +> show 1 +``` +Returns Students with Level of Friendship value `1` +```java +> sh 1 5 +``` +Returns Students with Level of Friendship value `1` and Students with Level of Friendship value `5` +// end::rate[] + +// tag::sort[] +[discrete] +=== Sort RC4 Students List: `sort [INDEX_TYPE]' [Since v1.5] + +Sorts the RC4 Students List according to the input index. There are three different sorting types available. + +*_Formats:_* +```java +sort INDEX_TYPE +``` + +**** +* Sort Type 1: Sorts the RC4 Students according to their Level of Friendship attribute. +* Sort Type 2: Sorts the RC4 Students according to their meeting date attribute. +* Sort Type 3: Sorts the RC4 Students according to their Birthday attribute. +* When sorting according to meet date, those sstudents whose meet date has passed or those students whom you are not meeting as of yet, will be moved to the bottom. +**** + +*_Examples_:* +```java +> sort 1 +``` +Returns `A sorted list of RC4 students according to their level of friendship` +// end::sort[] + +// tag::reminder[] + +=== Reminder features + +You might be wondering what else you can do with the calendar. The Reminder features listed below will allow you to set reminders for events, important dates or simply to-do tasks that will be reflected in the calendar for easy reference! + +[discrete] +=== Adding a reminder: `+reminder` or `+r` or `addreminder` [Since v1.4] + +You can try setting a reminder into the calendar in _CollegeZone_ by referring to the instructions below. + + +*_Formats:_* +```java +(+reminder | +r | addreminder) text/REMINDER_TEXT d/START_DATETIME e/END_DATETIME +``` + +*_Details:_* +**** +* A reminder will be added to _CollegeZone_ and it will be reflected in the Calendar. +* `START_DATETIME` & `END_DATETIME`: a datetime is a string that contains either a date, a time or a combination of both (in any order). If a date is not specified, then it will refer to today. If a time is not specified, then it will refer to the current time. +* Examples of valid `START_DATETIME` & `END_DATETIME`: + + - [date] [time] + + - 3pm + + - today + + - tmr + + - 10 Feb + + - thursday 8am + + - tomorrow 3pm + + - 14 Feb 2014 5.30am + + - 2/29/14 23:59 + + +[TIP] +You should use a date format of MM/DD/YYYY instead of DD/MM/YYYY if you choose to +use this date format for the START_DATETIME or END_DATETIME +**** + +*_Examples_:* +```java ++r text/Eat vitamins d/4/25/2018 8am e/4/25/2018 8.10am +``` +Returns `Eat vitamins` in the calendar on `25th April 2018` from `8am` to `8.10am` + +```java ++reminder text/Dental appointment d/next thurs 3pm e/next thurs 4pm +``` +Returns `Dental appointment` in the calendar according to `START_DATETIME` & `END_DATETIME` + +Figure below portrays what should be seen after the command is executed correctly: + +.Execution of +r text/Eat vitamins d/5/5/2018 8am e/5/5/2018 8.10am +image::addReminder.PNG[width="1500"] + +[discrete] +=== Deleting a reminder: `-reminder` or `-r` or `deletereminder` [Since v1.4] + +If you made a mistake or have reminders that are already due, you can also delete it away from the calendar by referring to the instructions below. + +*_Formats:_* +```java +(-reminder | -r | deletereminder) text/REMINDER_TEXT d/START_DATETIME +``` + +*_Details:_* +**** +* Deletes a reminder from _CollegeZone_ and the reminder will not be reflected in the Calendar anymore. +* `REMINDER_TEXT`: a string that contains the reminder's title +* `START_DATETIME`: a datetime is a string that contains either a date, a time or a combination of both (in any order). If a date is not specified, then it will refer to today. If a time is not specified, then it will refer to the current time. +* Examples of valid `START_DATETIME`: + + - [date] [time] + + - 3pm + + - today + + - tmr + + - 10 Feb + + - thursday 8am + + - tomorrow 3pm + + - 14 Feb 2014 5.30am + + - 2/29/14 23:59 + + +[TIP] +You should use a date format of MM/DD/YYYY instead of DD/MM/YYYY if you choose to +use this date format for START_DATETIME +**** + +*_Examples:_* + +```java +-r text/Eat vitamins d/4/25/2018 8am +``` +Deletes reminder `Eat vitamins` set on `25th April 2018` at `8am` + +```java +-reminder text/Dental appointment d/tmr +``` + +Deletes reminder `Dental appointment` according to `START_DATETIME` + +Figure below portrays what should be seen after the command is executed correctly: + +.Execution of -r text/Eat vitamins d/5/5/2018 8am +image::delReminder.PNG[width="1500"] + +// end::reminder[] + + +=== Goal features + +//tag::goalfeatures[] +[discrete] +=== Adding a goal: `+goal` or `+g` or `addgoal` [Since v1.3] +Currently, your goal page is empty! Try adding a goal entry that you have into _CollegeZone_ using the `+goal`, `+g` or `addgoal` command. The new goal added will be reflected in _CollegeZone_. + + +*_Formats:_* +```java +(+goal | +g | addgoal) impt/IMPORTANCE_LEVEL text/GOAL_TEXT +``` + +[NOTE] +==== +`IMPORTANCE_LEVEL` must be a positive integer ranging from 1 to 10. +==== -* `list` + -`select 2` + -Selects the 2nd person in the address book. -* `find Betsy` + -`select 1` + -Selects the 1st person in the results of the `find` command. +*_Examples_:* +```java +> +goal impt/3 text/lose weight! +``` +```java +> +g impt/2 text/meetup with close friends more often =) +``` +```java +> addgoal impt/1 text/learn how to bake cheesecake +``` +The figure below portrays what should be seen after the command is executed correctly: + +.Execution of +goal text/get an internship this summer impt/10 +image::AddGoal.PNG[width="1000"] + +[discrete] +=== Sorting goals: `sortgoal` or `sgoal` [Since v1.5] + +It is a hassle to locate the goals you're interested in if you have too many goals in _CollegeZone_. Try sorting the goals that you have using the `sortgoal` or `sgoal` command. + + +*_Formats:_* +```java +(sortgoal | sgoal) f/GOAL_FIELD o/ORDER +``` + +[NOTE] +==== +`GOAL_FIELD` can only be 'startdatetime', 'completion' or 'importance'. +==== +[NOTE] +==== +`ORDER` can only be either 'ascending' or 'descending'. +==== + +*_Examples_:* +```java +> sortgoal f/startdatetime o/ascending +``` +Sorts the goal list based on start date time in ascending order. + +```java +> sgoal f/completion o/descending +``` +Sorts the goal list based on completion in descending order. + +The figure below portrays what should be seen after the command is executed correctly: + +.Execution of sortgoal f/importance o/ascending +image::SortGoal.PNG[width="1000"] + +[discrete] +=== Editing a goal: `~goal` or `~g` or `editgoal` [Since v1.4] +If you've added a goal entry incorrectly, try using the `~goal`, `~g` or `editgoal` command to edit the goals you have. + + +*_Formats:_* +```java +(~goal | ~g | editgoal) INDEX [impt/IMPORTANCE_LEVEL] [text/GOAL_TEXT] +``` + +**** +* Edits the goal at the specified `INDEX`. The index refers to the index number shown in the goal listing. The index *must be a positive integer* 1, 2, 3, ... +* At least one of the optional fields must be provided. +* Existing values will be updated to the input values. +**** + +*_Examples_:* +```java +> ~goal 2 impt/1 +``` +Edits the goal importance level of the 2nd reminder to be `1`. + +```java +> sgoal f/importance o/descending +> ~g 1 impt/3 text/learn yoga +``` +Edits the goal text and importance level of the 1st student in the sorted goal list to be `learn yoga` and `1` respectively. + +```java +editgoal 5 text/swim at least twice a month +``` +Edits the goal text of the 5th student to be `swim at least twice a month`. + +The figure below portrays what should be seen after the command is executed correctly: + +.Execution of ~g 2 impt/10 +image::EditGoal.PNG[width="1000"] + +[discrete] +=== Deleting a goal: `-goal` or `-g` or `deletegoal` +If you've added a specific goal that you're unlikely to complete and want to remove it from _CollegeZone_, try using the `-goal`, `-g` or `deletegoal` command to delete a goal entry that you have. + +*_Formats:_* +```java +(-goal | -g | deletegoal) INDEX +``` + +**** +* Deletes the goal at the specified `INDEX`. +* The index refers to the index number shown in the most recent goal listing. +* The index *must be a positive integer* 1, 2, 3, ... +**** + +*_Examples_:* +```java +> -goal 2 +``` +Deletes the 2nd goal in _CollegeZone's_ goal listing. + + +```java +> sortgoal f/completion o/ascending +> -g 4 +``` +Deletes the 4th goal in _CollegeZone's_ sorted goal listing. + + +```java +> deletegoal 1 +``` +Deletes the 1st goal in _CollegeZone's_ goal listing. + +The figure below portrays what should be seen after the command is executed correctly: + +.Execution of -goal 4 +image::DeleteGoal.PNG[width="1000"] + +// end::goalfeatures[] + +//tag::goalcompletion[] +[discrete] +=== Completing a goal: `!goal` or `!g` or `completegoal` +Once you've completed a goal, indicate completion of an existing goal in _CollegeZone_ using the `!goal`, `!g` or `completegoal` command. + + +*_Formats:_* +```java +(!goal | !g | completegoal) INDEX +``` + +**** +* Indicates completion of the goal at the specified `INDEX`. +* The index refers to the index number shown in the most recent goal listing. +* The index *must be a positive integer* 1, 2, 3, ... +**** + +*_Examples_:* +```java +> !goal 2 +``` +Indicates completion of the 2nd goal in _CollegeZone's_ goal listing. + +```java +> sgoal f/importance o/descending +> !g 4 +``` +Indicates completion of the 4th goal in _CollegeZone's_ sorted goal listing. + +```java +> completegoal 1 +``` +Indicates completion of the 1st goal in _CollegeZone's_ goal listing. + +The figure below portrays what should be seen after the command is executed correctly: + +.Execution of !g 4 +image::CompleteGoal.PNG[width="1000"] + + +[NOTE] +==== +The goal percentage bar changes. +==== + +[discrete] +=== Revert completing a goal: `-!goal` or `-!g` or `ongoinggoal` +If you had an ongoing goal that is wrongly indicated that it's completed, do not fret, simply indicate that an existing goal you have is still ongoing in _CollegeZone_ using the `-!goal`, `-!g` or `ongoinggoal` command. + + +*_Formats:_* +```java +(-!goal | -!g | ongoinggoal) INDEX +``` + +**** +* Indicates completion of the goal at the specified `INDEX`. +* The index refers to the index number shown in the most recent goal listing. +* The index *must be a positive integer* 1, 2, 3, ... +**** + +*_Examples_:* +```java +> -!goal 2 +``` +Indicates ongoing of the 2nd goal in _CollegeZone's_ goal listing. +```java +> sortgoal f/startdatetime o/ascending +> -!g 4 +``` +Indicates ongoing of the 4th goal in _CollegeZone's_ sorted goal listing. +```java +> ongoing 1 +``` +Indicates ongoing of the 1st goal in _CollegeZone's_ goal listing. + +The figure below portrays what should be seen after the command is executed correctly: + +.Execution of -!g 1 +image::OngoingGoal.PNG[width="1000"] + + +[NOTE] +==== +The goal percentage bar changes. +==== + +// end::goalcompletion[] + +=== Interesting features +// tag::theme[] +[discrete] +=== Changing CollegeZone theme : `theme` or `th` +_CollegeZone's_ default theme colour is not the only theme colour we have to offer! Try changing _CollegeZone's_ theme colour to your preferred one using the `theme` or `th` command. + + +.Dark Theme +image::CollegeZoneDarkTheme.JPG[width="1000"] + +.Bubblegum Theme +image::CollegeZoneBubblegumTheme.JPG[width="1000"] + +.Light Theme +image::CollegeZoneLightTheme.JPG[width="1000"] + +*_Formats:_* +```java +(theme | th) THEME_COLOUR +``` + +[NOTE] +==== +`THEME_COLOUR` can only be 'dark', 'bubblegum' and 'light'. +==== + +*_Examples_:* +```java +> theme light +``` +Changes the theme colour to light theme. + +```java +> th dark +``` +Changes the theme colour to dark theme. + +```java +> th bubblegum +``` +Changes the theme colour to bubblegum theme. +// end::theme[] + +//tag::auto[] + +==== AutoComplete CLI with: `tab` + + You can type a command and press Tab to auto bring out all the command parameters. + +//end::auto[] + +==== Listing entered commands : `history` or `h` -=== Listing entered commands : `history` Lists all the commands that you have entered in reverse chronological order. + -Format: `history` +*_Formats:_* +```java +(history | h) +``` [NOTE] ==== @@ -169,96 +799,270 @@ Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and ==== // tag::undoredo[] -=== Undoing previous command : `undo` +[discrete] +=== Undoing previous command : `undo` or `u` -Restores the address book to the state before the previous _undoable_ command was executed. + -Format: `undo` +Restores _CollegeZone_ to the state before the previous _undoable_ command was executed. + +*_Formats:_* +```java +(undo | u) +``` [NOTE] ==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). +Undoable commands: those commands that modify _CollegeZone's_ content (`add`, `delete`, `edit`, `clear`, `+goal`, `-goal`, `~goal`, `!goal`, `-!goal`). ==== -Examples: - -* `delete 1` + -`list` + -`undo` (reverses the `delete 1` command) + - -* `select 1` + -`list` + -`undo` + +*_Examples_:* +```java +> delete 1 +> list +> undo +``` +Reverses the `delete 1` command + +```java +> select 1 +> list +> undo +``` The `undo` command fails as there are no undoable commands executed previously. -* `delete 1` + -`clear` + -`undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + +```java +> delete 1 +> clear +> undo +> undo +``` +Reverses both the `clear` command and the `delete 1` command -=== Redoing the previously undone command : `redo` +[discrete] +=== Redoing the previously undone command : `redo` or `r` Reverses the most recent `undo` command. + -Format: `redo` - -Examples: - -* `delete 1` + +*_Formats:_* +```java +(redo | r) +``` + +*_Examples_:* +```java +> delete 1 +> undo +> redo +``` `undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + +`redo` (reapplies the `delete 1` command) -* `delete 1` + -`redo` + +```java +> delete 1 +> redo +``` The `redo` command fails as there are no `undo` commands executed previously. -* `delete 1` + -`clear` + +```java +> delete 1 +> clear +> undo +> undo +> redo +> redo +``` `undo` (reverses the `clear` command) + `undo` (reverses the `delete 1` command) + `redo` (reapplies the `delete 1` command) + `redo` (reapplies the `clear` command) + // end::undoredo[] -=== Clearing all entries : `clear` +[discrete] +=== Clearing all entries : `clear` or `c` -Clears all entries from the address book. + -Format: `clear` +Clears all entries from _CollegeZone_. + +*_Formats:_* +```java +(clear | c) +``` +[discrete] === Exiting the program : `exit` Exits the program. + -Format: `exit` +*_Formats:_* +`exit` +[discrete] === Saving the data -Address book data are saved in the hard disk automatically after any command that changes the data. + +_CollegeZone_ data are saved in the hard disk automatically after any command that changes the data. + There is no need to save manually. +=== Upcoming features // tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +[discrete] +=== Encrypting data files [Coming in v2.0] _{explain how the user can enable/disable data encryption}_ // end::dataencryption[] +// tag::editreminder[] +[discrete] +=== Editing a reminder [Coming in v2.0] +Edits an existing reminder in CollegeZone. + +Format: `~r INDEX [text/REMINDER_TEXT] [d/START_DATETIME] [e/END_DATETIME]` + +**** +* Edits the reminder at the specified `INDEX`. The index refers to the index number shown in the reminder listing. The index *must be a positive integer* 1, 2, 3, ... +* At least one of the optional fields must be provided. +* Existing values will be updated to the input values. +**** + +Examples: + +* `~r 4 text/CS2103 exam coming in 1 week` + +Edits the reminder text of the 4th reminder to be `CS2103 exam coming in 1 week`. +* `~r 2 text/water plants d/tmr 7am` + +Edits the reminder text and datetime of the 2nd student to be `water plants` and `tmr 7am` respectively. + +// end::editreminder[] + +// tag::debv2.0[] +[discrete] +=== Adding subgoals [Coming in v2.0] + +By using this command, you are able to add subgoals to the goals you currently have. + +[discrete] +=== Adding of timetable into CollegeZone [Coming in v2.0] +Allows you to add your NUS timetable schedule for the semester into _CollegeZone_. +// end::debv2.0[] + == FAQ *Q*: How do I transfer my data to another Computer? + -*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. +*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous _CollegeZone_ folder. == Command Summary - -* *Add* `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + -e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -* *Clear* : `clear` -* *Delete* : `delete INDEX` + -e.g. `delete 3` -* *Edit* : `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + -e.g. `edit 2 n/James Lee e/jameslee@example.com` -* *Find* : `find KEYWORD [MORE_KEYWORDS]` + -e.g. `find James Jake` -* *List* : `list` -* *Help* : `help` -* *Select* : `select INDEX` + -e.g.`select 2` -* *History* : `history` -* *Undo* : `undo` -* *Redo* : `redo` +The table below provides a quick summary of all the commands available in _CollegeZone_. + +[width="90%",cols="20%,<22%,<23%,<25%",options="header",] +|======================================================================= +|Command |Function |Format |Example + +|*Add* |Adds a student entry|`add n/NAME p/PHONE_NUMBER */LEVEL_OF_FRIENDSHIP b/BIRTHDAY u/UNIT_NUMBER [cca/CCA]... [t/TAG]...` + +OR + +`a n/NAME p/PHONE_NUMBER */LEVEL_OF_FRIENDSHIP b/BIRTHDAY u/UNIT_NUMBER [cca/CCA]... [t/TAG]...` +|`add n/James Ho p/22224444 */3 b/14-3-1995 u/01-111 cca/Choir t/friend t/colleague` + +|*Add Goal* |Adds a goal entry |`+goal impt/IMPORTANCE_LEVEL text/GOAL_TEXT` + +OR + +`+g impt/IMPORTANCE_LEVEL text/GOAL_TEXT` + +OR + +`addgoal impt/IMPORTANCE_LEVEL text/GOAL_TEXT` + +|`+goal impt/3 text/lose weight!` + +|*Add Reminder* |Adds a reminder entry |`+reminder text/REMINDER_TEXT d/START_DATETIME e/END_DATETIME` + +OR + +`+r text/REMINDER_TEXT d/START_DATETIME e/END_DATETIME` + +OR + +`addreminder text/REMINDER_TEXT d/START_DATETIME e/END_DATETIME` + +|`+reminder text/Eat pills d/4/25/2018 8am e/4/25/2018 8.10am` + +|*Clear* |Clears all student and goal entries |`clear` OR `c`| + +|*Complete Goal* |Complete a goal entry |`!goal INDEX` + +OR + +`!g INDEX` + +OR + +`completegoal INDEX` + +|`!goal 1` + +|*Delete* |Deletes a student entry |`delete INDEX` OR `d INDEX` +|`delete 3` + +|*Delete Goal* |Deletes a goal entry |`-goal INDEX` + +OR + +`-g INDEX` + +OR + +`deletegoal INDEX` + +|`-goal 2` + +|*Delete Reminder* |Deletes a reminder entry |`-reminder text/REMINDER_TEXT d/START_DATETIME` + +OR + +`-r text/REMINDER_TEXT d/START_DATETIME` + +OR + +`deletereminder text/REMINDER_TEXT d/START_DATETIME` + +|`-reminder text/Eat pills d/4/25/2018 8am` + +|*Edit* |Edits a person details |`edit INDEX [n/NAME] [p/PHONE_NUMBER] [*/LEVEL_OF_FRIENDSHIP] [b/BIRTHDAY] [u/UNIT_NUMBER] [cca/CCA]... [t/TAG]...` + +OR + +`e INDEX [n/NAME] [p/PHONE_NUMBER][ */LEVEL_OF_FRIENDSHIP] [b/BIRTHDAY] [u/UNIT_NUMBER] [cca/CCA]... [t/TAG]...` + +|`edit 2 n/James Lee cca/waterpolo` + +|*Edit Goal* |Edits a goal entry |`~goal INDEX [impt/IMPORTANCE_LEVEL] [text/GOAL_TEXT]` + +OR + +`~g INDEX [impt/IMPORTANCE_LEVEL] [text/GOAL_TEXT]` + +OR + +`editgoal INDEX [impt/IMPORTANCE_LEVEL] [text/GOAL_TEXT]` + +|`~goal 2 impt/1` + +|*Exit* |Exits _CollegeZone_ |`exit` | + +|*Find* |Finds a student by keyword |`find n/KEYWORD [MORE_KEYWORDS]` + +OR + +`f n/KEYWORD [MORE_KEYWORDS]` + +OR + +`find t/KEYWORD [MORE_KEYWORDS]` + +OR + +`f t/KEYWORD [MORE_KEYWORDS]` + +|`find n/James Jake` + +|*Help* |Opens the help page |`help`| + +|*History* |Lists previously entered commands |`history` + +OR + +`h` +| + +|*List* |Lists all students |`list` + +OR + +`l` | + +|*Meet* |Adds meet up date with a student |`meet INDEX d/DD-MM-YYYY` + +OR + +`m INDEX d/DD-MM-YYYY` +| + +|*Rate* |Rates one or more people|`rate INDEX(s) [LEVELOFFRIENDSHIP]` OR + +`rt INDEX(s) [LEVELOFFRIENDSHIP]`| rate 1 3 */5 + +|*Redo* |Redo previous command |`redo` | + +|*Revert complete Goal* |Revert a complete a goal entry |`-!goal INDEX` + +OR + +`-!g INDEX` + +OR + +`ongoinggoal INDEX` + +|`-!goal 1` + +|*Seek* |Seek RA(s) of students by keyword|`seek [keyword]` OR + +`sh [keyword]`| seek james + +|*Select* |Selects the student and goal entry |`select INDEX` + +OR + +`s INDEX` +|`select 2` + +|*Show* |Show students by level of friendship|`show [LEVELOFFRIENDSHIP]` OR + +`sh [LEVELOFFRIENDSHIP]`| show 5 + +|*Sort* |Sorts the RC4 Students list in 3 different ways|`sort [SORT TYPE]`| sort 1 + +|*Sort Goal* |Sorts the goal list |`sortgoal f/GOAL_FIELD o/ORDER` + +OR + +`sgoal f/GOAL_FIELD o/ORDER` + +|`sortgoal f/startdatetime o/ascending` + +|*Theme* |Switch theme colour of _CollegeZone_ |`theme THEME_COLOUR` + +OR + +`th THEME_COLOUR` + +|`theme light` + +|*Undo* |Undo previous command |`undo` | diff --git a/docs/diagrams/GoalHighLevelSequenceDiagram.jpg b/docs/diagrams/GoalHighLevelSequenceDiagram.jpg new file mode 100644 index 000000000000..c67cdf3f879f Binary files /dev/null and b/docs/diagrams/GoalHighLevelSequenceDiagram.jpg differ diff --git a/docs/diagrams/GoalHighLevelSequenceDiagrams.pptx b/docs/diagrams/GoalHighLevelSequenceDiagrams.pptx new file mode 100644 index 000000000000..4724051ac089 Binary files /dev/null and b/docs/diagrams/GoalHighLevelSequenceDiagrams.pptx differ diff --git a/docs/diagrams/GoalModelComponentClassDiagram.JPG b/docs/diagrams/GoalModelComponentClassDiagram.JPG new file mode 100644 index 000000000000..fe88abba4a53 Binary files /dev/null and b/docs/diagrams/GoalModelComponentClassDiagram.JPG differ diff --git a/docs/diagrams/GoalModelComponentClassDiagram.pptx b/docs/diagrams/GoalModelComponentClassDiagram.pptx new file mode 100644 index 000000000000..0aa3a2eb0c3f Binary files /dev/null and b/docs/diagrams/GoalModelComponentClassDiagram.pptx differ diff --git a/docs/diagrams/RCPersonClass.JPG b/docs/diagrams/RCPersonClass.JPG new file mode 100644 index 000000000000..9c8f73c1b9f4 Binary files /dev/null and b/docs/diagrams/RCPersonClass.JPG differ diff --git a/docs/images/AddGoal.PNG b/docs/images/AddGoal.PNG new file mode 100644 index 000000000000..76037db4767d Binary files /dev/null and b/docs/images/AddGoal.PNG differ diff --git a/docs/images/AddGoalSeqDiagram.PNG b/docs/images/AddGoalSeqDiagram.PNG new file mode 100644 index 000000000000..ce135774448f Binary files /dev/null and b/docs/images/AddGoalSeqDiagram.PNG differ diff --git a/docs/images/CollegeZoneBubblegumTheme.JPG b/docs/images/CollegeZoneBubblegumTheme.JPG new file mode 100644 index 000000000000..84b78f0d57bd Binary files /dev/null and b/docs/images/CollegeZoneBubblegumTheme.JPG differ diff --git a/docs/images/CollegeZoneDarkTheme.JPG b/docs/images/CollegeZoneDarkTheme.JPG new file mode 100644 index 000000000000..5b1ccbe4fc4e Binary files /dev/null and b/docs/images/CollegeZoneDarkTheme.JPG differ diff --git a/docs/images/CollegeZoneGoalModelClassDiagram.JPG b/docs/images/CollegeZoneGoalModelClassDiagram.JPG new file mode 100644 index 000000000000..168a8fb9d55b Binary files /dev/null and b/docs/images/CollegeZoneGoalModelClassDiagram.JPG differ diff --git a/docs/images/CollegeZoneLightTheme.JPG b/docs/images/CollegeZoneLightTheme.JPG new file mode 100644 index 000000000000..06e3518482b5 Binary files /dev/null and b/docs/images/CollegeZoneLightTheme.JPG differ diff --git a/docs/images/CompleteGoal.PNG b/docs/images/CompleteGoal.PNG new file mode 100644 index 000000000000..f94c6673aaea Binary files /dev/null and b/docs/images/CompleteGoal.PNG differ diff --git a/docs/images/CompleteGoalSeqDiagram.PNG b/docs/images/CompleteGoalSeqDiagram.PNG new file mode 100644 index 000000000000..b86cdb64534f Binary files /dev/null and b/docs/images/CompleteGoalSeqDiagram.PNG differ diff --git a/docs/images/DeleteGoal.PNG b/docs/images/DeleteGoal.PNG new file mode 100644 index 000000000000..f578f02b024c Binary files /dev/null and b/docs/images/DeleteGoal.PNG differ diff --git a/docs/images/DeleteGoalSeqDiagram.PNG b/docs/images/DeleteGoalSeqDiagram.PNG new file mode 100644 index 000000000000..0bc63861d492 Binary files /dev/null and b/docs/images/DeleteGoalSeqDiagram.PNG differ diff --git a/docs/images/DownloadJar.JPG b/docs/images/DownloadJar.JPG new file mode 100644 index 000000000000..d7d62bab3499 Binary files /dev/null and b/docs/images/DownloadJar.JPG differ diff --git a/docs/images/EditGoal.PNG b/docs/images/EditGoal.PNG new file mode 100644 index 000000000000..5e0e6604a328 Binary files /dev/null and b/docs/images/EditGoal.PNG differ diff --git a/docs/images/EditGoalSeqDiagram.PNG b/docs/images/EditGoalSeqDiagram.PNG new file mode 100644 index 000000000000..25e4bd327f93 Binary files /dev/null and b/docs/images/EditGoalSeqDiagram.PNG differ diff --git a/docs/images/GoalHighLevelSequenceDiagram.jpg b/docs/images/GoalHighLevelSequenceDiagram.jpg new file mode 100644 index 000000000000..c67cdf3f879f Binary files /dev/null and b/docs/images/GoalHighLevelSequenceDiagram.jpg differ diff --git a/docs/images/GoalModelComponentClassDiagram.JPG b/docs/images/GoalModelComponentClassDiagram.JPG new file mode 100644 index 000000000000..fe88abba4a53 Binary files /dev/null and b/docs/images/GoalModelComponentClassDiagram.JPG differ diff --git a/docs/images/LogicCommandClassDiagram_Sort.png b/docs/images/LogicCommandClassDiagram_Sort.png new file mode 100644 index 000000000000..8113c9dc8974 Binary files /dev/null and b/docs/images/LogicCommandClassDiagram_Sort.png differ diff --git a/docs/images/ModelComponentCollegeZone.PNG b/docs/images/ModelComponentCollegeZone.PNG new file mode 100644 index 000000000000..c8abff0288c1 Binary files /dev/null and b/docs/images/ModelComponentCollegeZone.PNG differ diff --git a/docs/images/OngoingGoal.PNG b/docs/images/OngoingGoal.PNG new file mode 100644 index 000000000000..6704ba2008dc Binary files /dev/null and b/docs/images/OngoingGoal.PNG differ diff --git a/docs/images/RC4ModelComponentClass.JPG b/docs/images/RC4ModelComponentClass.JPG new file mode 100644 index 000000000000..691aaed96b3e Binary files /dev/null and b/docs/images/RC4ModelComponentClass.JPG differ diff --git a/docs/images/RCPersonClass.JPG b/docs/images/RCPersonClass.JPG new file mode 100644 index 000000000000..9c8f73c1b9f4 Binary files /dev/null and b/docs/images/RCPersonClass.JPG differ diff --git a/docs/images/RCPersonClass1.jpg b/docs/images/RCPersonClass1.jpg new file mode 100644 index 000000000000..9c8f73c1b9f4 Binary files /dev/null and b/docs/images/RCPersonClass1.jpg differ diff --git a/docs/images/RateCommandClassDiagram.jpg b/docs/images/RateCommandClassDiagram.jpg new file mode 100644 index 000000000000..484f8a50abff Binary files /dev/null and b/docs/images/RateCommandClassDiagram.jpg differ diff --git a/docs/images/ReminderClassDiagram.PNG b/docs/images/ReminderClassDiagram.PNG new file mode 100644 index 000000000000..9728d19b433a Binary files /dev/null and b/docs/images/ReminderClassDiagram.PNG differ diff --git a/docs/images/SortGoal.PNG b/docs/images/SortGoal.PNG new file mode 100644 index 000000000000..d267a150cb71 Binary files /dev/null and b/docs/images/SortGoal.PNG differ diff --git a/docs/images/SortGoalSeqDiagram.PNG b/docs/images/SortGoalSeqDiagram.PNG new file mode 100644 index 000000000000..1e7ed783ddbc Binary files /dev/null and b/docs/images/SortGoalSeqDiagram.PNG differ diff --git a/docs/images/SortPersonSdForLogic.png b/docs/images/SortPersonSdForLogic.png new file mode 100644 index 000000000000..47890f74166e Binary files /dev/null and b/docs/images/SortPersonSdForLogic.png differ diff --git a/docs/images/StorageComponentCollegeZone.PNG b/docs/images/StorageComponentCollegeZone.PNG new file mode 100644 index 000000000000..b1bff747df5a Binary files /dev/null and b/docs/images/StorageComponentCollegeZone.PNG differ diff --git a/docs/images/UI.JPG b/docs/images/UI.JPG new file mode 100644 index 000000000000..b68fada90e52 Binary files /dev/null and b/docs/images/UI.JPG differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5ec9c527b49c..b68fada90e52 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UserGuideLabelledUI.PNG b/docs/images/UserGuideLabelledUI.PNG new file mode 100644 index 000000000000..c9fa390616cd Binary files /dev/null and b/docs/images/UserGuideLabelledUI.PNG differ diff --git a/docs/images/addReminder.PNG b/docs/images/addReminder.PNG new file mode 100644 index 000000000000..c539aed0a744 Binary files /dev/null and b/docs/images/addReminder.PNG differ diff --git a/docs/images/addReminderSeqDiagram.PNG b/docs/images/addReminderSeqDiagram.PNG new file mode 100644 index 000000000000..6849c14582b8 Binary files /dev/null and b/docs/images/addReminderSeqDiagram.PNG differ diff --git a/docs/images/appveyor/DownloadJar.JPG b/docs/images/appveyor/DownloadJar.JPG new file mode 100644 index 000000000000..d7d62bab3499 Binary files /dev/null and b/docs/images/appveyor/DownloadJar.JPG differ diff --git a/docs/images/deborahlow97.jpg b/docs/images/deborahlow97.jpg new file mode 100644 index 000000000000..f3b01d4dcd26 Binary files /dev/null and b/docs/images/deborahlow97.jpg differ diff --git a/docs/images/delReminder.PNG b/docs/images/delReminder.PNG new file mode 100644 index 000000000000..317ff383ce85 Binary files /dev/null and b/docs/images/delReminder.PNG differ diff --git a/docs/images/delReminderSeqDiagram.PNG b/docs/images/delReminderSeqDiagram.PNG new file mode 100644 index 000000000000..4ca3f7323569 Binary files /dev/null and b/docs/images/delReminderSeqDiagram.PNG differ diff --git a/docs/images/fuadsahmawi.jpg b/docs/images/fuadsahmawi.jpg new file mode 100644 index 000000000000..ba9c19d52fc7 Binary files /dev/null and b/docs/images/fuadsahmawi.jpg differ diff --git a/docs/images/lejolly.jpg b/docs/images/lejolly.jpg deleted file mode 100644 index 2d1d94e0cf5d..000000000000 Binary files a/docs/images/lejolly.jpg and /dev/null differ diff --git a/docs/images/m133225.jpg b/docs/images/m133225.jpg deleted file mode 100644 index fd14fb94593a..000000000000 Binary files a/docs/images/m133225.jpg and /dev/null differ diff --git a/docs/images/sham-sheer.jpg b/docs/images/sham-sheer.jpg new file mode 100644 index 000000000000..a7e116f7d6ad Binary files /dev/null and b/docs/images/sham-sheer.jpg differ diff --git a/docs/images/yijinl.jpg b/docs/images/yijinl.jpg deleted file mode 100644 index adbf62ad9406..000000000000 Binary files a/docs/images/yijinl.jpg and /dev/null differ diff --git a/docs/images/zuweitrack.jpg b/docs/images/zuweitrack.jpg new file mode 100644 index 000000000000..8eb0346f43ac Binary files /dev/null and b/docs/images/zuweitrack.jpg differ diff --git a/docs/team/deborahlow97.adoc b/docs/team/deborahlow97.adoc new file mode 100644 index 000000000000..3e942b329f17 --- /dev/null +++ b/docs/team/deborahlow97.adoc @@ -0,0 +1,65 @@ += Deborah Low Shi Lei - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets +:collatedDir: ../../collated + +== Project: CollegeZone +CollegeZone is a desktop address book application used by National University of Singapore Residential College 4 (RC4) students. We aim to make RC4 student’s school life much easier and more convenient. + +The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 20 kLoC. + +*Code contributed*: + +link:{collatedDir}/main/deborahlow97.md[Functional code] + +link:{collatedDir}/test/deborahlow97.md[Test code] + + +== Summary of contributions + +=== Major enhancement: added Add, Edit, Sort and Delete goal command +** What it does: Allows user to add, edit and delete goals into CollegeZone. It also allows user to sort the goals that they have based on the field they want it sorted in. It includes keeping track of when the goals started, ended, it's importance level to user and it's completion status. +** External behavior: + +include::../UserGuide.adoc[tag=goalfeatures] + +** Justification: This feature improves the product significantly because RC4 students will have goals that they want to accomplish in life, especially for new RC4 students, they enter NUS with many goals they want to achieve. Having this feature allows them to record down the goals and track the status of it. + +=== Major enhancement: added Complete and RevertComplete goal command +** What it does: Allows user to update the completion status of the existing goals they have in CollegeZone. +** External behavior: + +include::../UserGuide.adoc[tag=goalcompletion] + +** Justification: This feature improves the product significantly because RC4 students will have be able to indicate completion status of goals that they have. + +=== Minor enhancement: added a theme command that allows the user to change the theme colour of CollegeZone. +** Justification: This feature allows RC4 students to switch to a different theme colour of their liking. +** External behavior: + +include::../UserGuide.adoc[tag=theme] + +=== Minor enhancement: added birthday, level of friendship, unit number and ccas field +** Justification: The addition of this field provides user with more comprehensive contact details to add into CollegeZone. + +=== Proposed enhancements: +include::../UserGuide.adoc[tag=debv2.0] + +** Adding Sub-goals +** Adding of timetable into CollegeZone + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=addgoal] +include::../DeveloperGuide.adoc[tag=sortgoal] +include::../DeveloperGuide.adoc[tag=theme] + +=== Other Contributions: +** Project management: +*** Managed releases `v1.3` - `v1.4.1` (3 releases) on GitHub +*** +** Enhancements to existing features: +*** Updated the GUI color scheme (Pull requests https://github.com/CS2103JAN2018-T09-B2/main/pull/204/files[#204]) +** Documentation: +*** Did cosmetic tweaks to existing contents of the User Guide (https://github.com/CS2103JAN2018-T09-B2/main/pull/198/files[#198]) +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com[#12], https://github.com[#32], https://github.com[#19], https://github.com[#42] +*** Reported bugs and suggestions for other teams in the class (examples: https://github.com/CS2103JAN2018-W14-B3/main/issues/102[1], https://github.com/CS2103JAN2018-W14-B3/main/issues/99[2], https://github.com/CS2103JAN2018-W14-B3/main/issues/103[3]) +** Tools: +*** Integrated a third party library (Natty) to the project (https://github.com/CS2103JAN2018-T09-B2/main/issues/64[#64]) diff --git a/docs/team/fuadsahmawi.adoc b/docs/team/fuadsahmawi.adoc new file mode 100644 index 000000000000..6d2447c6cdbf --- /dev/null +++ b/docs/team/fuadsahmawi.adoc @@ -0,0 +1,63 @@ += Fuad B Sahmawi - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +image::fuadsahmawi.jpg[width="150", align="left"] +(https://github.com/fuadsahmawi[GitHub]) + +Welcome to my project portfolio. This document summarises my contributions of every software-related project that I have +been involved with. The link to my GitHub page can be found above. This is where you can find the repository of every +project mentioned below. + +== PROJECT: CollegeZone + +--- + +== Overview + +CollegeZone is a desktop application for NUS Residential College 4 (RC4) students. It has a Graphical User Interface (GUI) +but most of the user interactions happen using a Command Line Interface (CLI). +This application is catered for an RC4 student to manage their contacts with other RC4 students and to manage their tasks, +just like a digital organiser. + +== Summary of contributions + +* *Major enhancement*: Added *the ability to add & delete reminders in a Calendar interface* +** What it does: allows the RC4 student to add and delete reminders in a calendar, which will be marked with different colours, depending on whether the specific reminder is due or not due. +** Justification: This feature improves the product significantly because RC4 students, especially freshmen, will be new to University and College life and being able to note down tasks and reminders will greatly assist them in organising their day-to-day activities. +** Highlights: This feature involves creating a new class with new attributes that were not in the original AddressBook. Interacting with the CalendarFX Application Programming Interface(API) to be able to display the reminders also posed challenges in terms of implementation. +** Credits:(https://github.com/CS2103JAN2018-T09-B2/main/pull/116[CalendarFX API], http://natty.joestelmach.com/try.jsp[Natty API]) + +* *Minor enhancement*: Added additional functionality to the find command, which allows RC4 students to find their contacts by tags. + +* *Code contributed*: [https://github.com/CS2103JAN2018-T09-B2/main/blob/master/collated/main/fuadsahmawi.md[Functional code]] [https://github.com/CS2103JAN2018-T09-B2/main/blob/master/collated/test/fuadsahmawi.md[Test code]] + +* *Other contributions*: + +** Project management: +*** Managed releases `v1.5rc` & `v1.5` (2 releases) on (https://github.com/CS2103JAN2018-T09-B2/main/releases[GitHub]) +** Enhancements to existing features: +*** Updated the GUI to include a Calendar Panel(Pull request https://github.com/CS2103JAN2018-T09-B2/main/pull/116[#116]) +*** Added command aliases for commands that were originally in addressbook (Pull request https://github.com/CS2103JAN2018-T09-B2/main/pull/9[#9]) +** Documentation: +*** Did cosmetic tweaks to existing contents of the User Guide & Developer Guide (Pull requests https://github.com/CS2103JAN2018-T09-B2/main/pull/54[#54], https://github.com/CS2103JAN2018-T09-B2/main/pull/85[#85], https://github.com/CS2103JAN2018-T09-B2/main/pull/163[#163], https://github.com/CS2103JAN2018-T09-B2/main/pull/245[#245], https://github.com/CS2103JAN2018-T09-B2/main/pull/248[#248]) +** Tools: +*** Integrated a third party library and API (CalendarFX) to the project (Pull request https://github.com/CS2103JAN2018-T09-B2/main/pull/116[#116]) + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=reminder] +include::../UserGuide.adoc[tag=editreminder] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=reminder] diff --git a/docs/team/gohzuwei.adoc b/docs/team/gohzuwei.adoc new file mode 100644 index 000000000000..e3faccc94c82 --- /dev/null +++ b/docs/team/gohzuwei.adoc @@ -0,0 +1,59 @@ += Goh Zu Wei - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +image::zuweitrack.jpg[width="150", align="left"] +{empty}[https://github.com/zuweitrack[github]] + +== PROJECT: CollegeZone + +--- + +== Overview + +CollegeZone is a Address Book desktop application intended for students living on campus, specifically this application are for students living in an NUS Residential College, RC4. +RC4 students can use CollegeZone application through a Command Line Interface (CLI). +The purpose of CollegeZone is for RC4 students monitor and maintain friendships with their fellow residents and it serves as a personal planner as well, keeping track of their tasks. + +== Summary of contributions + +* *Major enhancement*: Added the *ability rate multiple people* by changing the level of friendship and *show people based on their level of friendships.* +** What it does: allows the RC4 student to rate multiple friends in CollegeZone and the level of friendship of these friends will be changed. +** Justification: An RC4 student may interact with several groups of people during the week, being able to rate multiple people at a go makes the process more convenient. This allows RC4 students to keep track of who their close friends are and helps them identify people who they may want to get closer with. + +** Highlights: The features show command followed by the rate command allows RC4 students to show friends of a particular level of friendship and change the level of friendships from that list of friends. It gives RC4 students the ease of rating people of a particular level of friendship + +* *Minor enhancement*: Added a seek RA command, which allows RC4 students to find the details of RC4 students and Resident Assistants (RA). + +* *Code contributed*: [https://github.com/CS2103JAN2018-T09-B2/main/blob/master/collated/main/zuweitrack.md[Functional code]] [https://github.com/CS2103JAN2018-T09-B2/main/blob/master/collated/test/zuweitrack.md[Test code]] + +* *Other contributions*: + +** Project management: +*** Collated team members code contributions (Pull request https://github.com/CS2103JAN2018-T09-B2/main/pull/232/files[#232]) +** Enhancements to existing features: +*** Enhancement to storage component, Added a new method for address book to be stored in a fixed temporary location. (Pull request https://github.com/CS2103JAN2018-T09-B2/main/pull/10/files[#10]) +** Documentation: +*** Build User and Developer Guides to HTML files using the asciidoctor plugin. + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +=== Major enhancement: added rate and show level of friendship command +include::../UserGuide.adoc[tag=rate] + + +=== Minor enhancement: added a seek RA command +include::../UserGuide.adoc[tag=seek] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=rate] diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc deleted file mode 100644 index 0dfa757e454b..000000000000 --- a/docs/team/johndoe.adoc +++ /dev/null @@ -1,71 +0,0 @@ -= John Doe - Project Portfolio -:imagesDir: ../images -:stylesDir: ../stylesheets - -== PROJECT: AddressBook - Level 4 - ---- - -== Overview - -AddressBook - Level 4 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -== Summary of contributions - -* *Major enhancement*: added *the ability to undo/redo previous commands* -** What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. -** Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. -** Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. -** Credits: _{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}_ - -* *Minor enhancement*: added a history command that allows the user to navigate to previous commands using up/down keys. - -* *Code contributed*: [https://github.com[Functional code]] [https://github.com[Test code]] _{give links to collated code files}_ - -* *Other contributions*: - -** Project management: -*** Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub -** Enhancements to existing features: -*** Updated the GUI color scheme (Pull requests https://github.com[#33], https://github.com[#34]) -*** Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests https://github.com[#36], https://github.com[#38]) -** Documentation: -*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com[#14] -** Community: -*** PRs reviewed (with non-trivial review comments): https://github.com[#12], https://github.com[#32], https://github.com[#19], https://github.com[#42] -*** Contributed to forum discussions (examples: https://github.com[1], https://github.com[2], https://github.com[3], https://github.com[4]) -*** Reported bugs and suggestions for other teams in the class (examples: https://github.com[1], https://github.com[2], https://github.com[3]) -*** Some parts of the history feature I added was adopted by several other class mates (https://github.com[1], https://github.com[2]) -** Tools: -*** Integrated a third party library (Natty) to the project (https://github.com[#42]) -*** Integrated a new Github plugin (CircleCI) to the team repo - -_{you can add/remove categories in the list above}_ - -== Contributions to the User Guide - - -|=== -|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ -|=== - -include::../UserGuide.adoc[tag=undoredo] - -include::../UserGuide.adoc[tag=dataencryption] - -== Contributions to the Developer Guide - -|=== -|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ -|=== - -include::../DeveloperGuide.adoc[tag=undoredo] - -include::../DeveloperGuide.adoc[tag=dataencryption] - - -== PROJECT: PowerPointLabs - ---- - -_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/sham-sheer.adoc b/docs/team/sham-sheer.adoc new file mode 100644 index 000000000000..b4e1aa1dcf2f --- /dev/null +++ b/docs/team/sham-sheer.adoc @@ -0,0 +1,58 @@ += Shamsheer Ahamed - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: CollegeZone + +--- + +== Overview + +CollegeZone is a desktop application for NUS Residential College 4 (RC4) students. It has a Graphical User Interface (GUI) but most of the user interactions happen using a Command Line Interface (CLI). This application is catered for an RC4 resident to manage their contacts with other RC4 residents and to manage their tasks, just like a digital organiser. +== Summary of contributions + +* *Major enhancement*: Social CollegeZone: Meet Command and Sort Command +** What it does: allows the RC4 students to be set up meet dates in our CollegeZone Calendar and also easily keep track of upcoming meet up dates, birthdays with a click of a button. +** Justification: This feature was primarily added to provide a platform for RC4 students to be more social and make it easy for them to keep track of their meeting appointments and their friends birthdays to plan get togethers. +** Highlights: This feature involves creating a new attributes for the `Person` Class that were not in the original addressbook. Interacting with the CalendarFX API to be able to display the meet dates also posed challenges in terms of implementation. +** Credits: CalendarFX API + +* *Minor enhancement*: added an auto complete command that auto-completes a command with the press of `TAB`. + +* *Code contributed*: [https://github.com/CS2103JAN2018-T09-B2/main/blob/master/collated/main/sham-sheer.md[Functional code]] [https://github.com/CS2103JAN2018-T09-B2/main/blob/master/collated/test/sham-sheer.md[Test code]] + +* *Other contributions*: + +** Project management: +*** Managed releases `v1.5rc` (1 release) on GitHub +** Documentation: +*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com/CS2103JAN2018-T09-B2/main/pull/82[#82] +** Community: +*** +*** Reported bugs and suggestions for other teams in the class (examples: https://github.com/CS2103JAN2018-W11-B1/main/issues/151[1], https://github.com/CS2103JAN2018-W11-B1/main/issues/150[2], https://github.com/CS2103JAN2018-W11-B1/main/issues/154[3]) +** Tools: +*** Integrated a third party library and API (CalendarFX) to the project + +_{you can add/remove categories in the list above}_ + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== +include::../UserGuide.adoc[tag=sort] +include::../UserGuide.adoc[tag=meet] +include::../UserGuide.adoc[tag=auto] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== +include::../DeveloperGuide.adoc[tag=meetCommand] +include::../DeveloperGuide.adoc[tag=sortmech] + +--- + + diff --git a/null.backup b/null.backup new file mode 100644 index 000000000000..5fff83ab5290 --- /dev/null +++ b/null.backup @@ -0,0 +1,2 @@ + + diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index fa0800d55cb9..410ec2d3db7d 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -25,7 +25,7 @@ import seedu.address.model.ModelManager; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.UserPrefs; -import seedu.address.model.util.SampleDataUtil; +import seedu.address.model.util.SampleCollegeZone; import seedu.address.storage.AddressBookStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.Storage; @@ -40,7 +40,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 6, 0, true); + public static final Version VERSION = new Version(1, 4, 1, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -54,7 +54,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing CollegeZone ]==========================="); super.init(); config = initConfig(getApplicationParameter("config")); @@ -91,14 +91,14 @@ private Model initModelManager(Storage storage, UserPrefs userPrefs) { try { addressBookOptional = storage.readAddressBook(); if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + logger.info("Data file not found. Will be starting with a sample CollegeZone"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = addressBookOptional.orElseGet(SampleCollegeZone::getSampleCollegeZone); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); + logger.warning("Data file not in the correct format. Will be starting with an empty CollegeZone"); initialData = new AddressBook(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty CollegeZone"); initialData = new AddressBook(); } @@ -163,7 +163,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty CollegeZone"); initializedPrefs = new UserPrefs(); } @@ -183,13 +183,13 @@ private void initEventsCenter() { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting CollegeZone " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping CollegeZone ] ============================="); ui.stop(); try { storage.saveUserPrefs(userPrefs); diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/address/commons/core/Config.java index 8f4d737d0e24..9e1390bd0843 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/address/commons/core/Config.java @@ -11,7 +11,7 @@ public class Config { public static final String DEFAULT_CONFIG_FILE = "config.json"; // Config values customizable through config file - private String appTitle = "Address App"; + private String appTitle = "CollegeZone"; private Level logLevel = Level.INFO; private String userPrefsFilePath = "preferences.json"; diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e4695..6aebb6348ec3 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -6,8 +6,22 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; + + public static final String MESSAGE_INVALID_DATE_FORMAT = + "Start Date cannot be later than End Date or Start/End Date cannot be earlier than current date! \n%1$s"; + public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; + public static final String MESSAGE_INVALID_GOAL_DISPLAYED_INDEX = "The goal index provided is invalid"; + public static final String MESSAGE_INVALID_REMINDER_TEXT_DATE = "The reminder text or start date " + + "provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_RA_LISTED_OVERVIEW = "%1$d Searched Persons and " + + "their Resident Assistant(s) (RA) listed!"; + public static final String MESSAGE_INVALID_SORT_COMMAND_USAGE = "Sort command cannot be done on an empty " + + "goal list!"; + public static final String MESSAGE_GOAL_ONGOING_ERROR = "Goal is already ongoing."; + public static final String MESSAGE_GOAL_COMPLETED_ERROR = "Goal is already completed."; } diff --git a/src/main/java/seedu/address/commons/events/ui/ThemeSwitchRequestEvent.java b/src/main/java/seedu/address/commons/events/ui/ThemeSwitchRequestEvent.java new file mode 100644 index 000000000000..bed5fd1190a1 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/ThemeSwitchRequestEvent.java @@ -0,0 +1,20 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; + +//@@author deborahlow97 +/** + * Indicates that a theme switch is requested. + */ +public class ThemeSwitchRequestEvent extends BaseEvent { + public final String themeToChangeTo; + + public ThemeSwitchRequestEvent(String themeToChangeTo) { + this.themeToChangeTo = themeToChangeTo; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/address/logic/CommandFormatListUtil.java b/src/main/java/seedu/address/logic/CommandFormatListUtil.java new file mode 100644 index 000000000000..8cef5b257d22 --- /dev/null +++ b/src/main/java/seedu/address/logic/CommandFormatListUtil.java @@ -0,0 +1,81 @@ + +package seedu.address.logic; + +import java.util.ArrayList; +import java.util.Collections; + +import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddGoalCommand; +import seedu.address.logic.commands.AddReminderCommand; +import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.CompleteGoalCommand; +import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteGoalCommand; +import seedu.address.logic.commands.DeleteMeetCommand; +import seedu.address.logic.commands.DeleteReminderCommand; +import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.EditGoalCommand; +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.HistoryCommand; +import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.MeetCommand; +import seedu.address.logic.commands.OngoingGoalCommand; +import seedu.address.logic.commands.RateCommand; +import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.ShowLofCommand; +import seedu.address.logic.commands.SortCommand; +import seedu.address.logic.commands.SortGoalCommand; +import seedu.address.logic.commands.ThemeCommand; +import seedu.address.logic.commands.UndoCommand; + +//@@author sham-sheer +/** + * Initialises and returns a list which contains different command formats + */ +public final class CommandFormatListUtil { + private static ArrayList commandFormatList; + + public static ArrayList getCommandFormatList () { + commandFormatList = new ArrayList<>(); + createCommandFormatList(); + return commandFormatList; + } + + /** + * Creates commandFormatList for existing commands + */ + private static void createCommandFormatList() { + commandFormatList.add(AddCommand.COMMAND_FORMAT); + commandFormatList.add(AddGoalCommand.COMMAND_FORMAT); + commandFormatList.add(AddReminderCommand.COMMAND_WORD); + commandFormatList.add(ClearCommand.COMMAND_WORD); + commandFormatList.add(CompleteGoalCommand.COMMAND_WORD); + commandFormatList.add(DeleteCommand.COMMAND_WORD); + commandFormatList.add(DeleteGoalCommand.COMMAND_WORD); + commandFormatList.add(DeleteMeetCommand.COMMAND_WORD); + commandFormatList.add(DeleteReminderCommand.COMMAND_ALIAS_2); + commandFormatList.add(EditCommand.COMMAND_FORMAT); + commandFormatList.add(EditGoalCommand.COMMAND_WORD); + commandFormatList.add(ExitCommand.COMMAND_WORD); + commandFormatList.add(FindCommand.COMMAND_FORMAT); + commandFormatList.add(HelpCommand.COMMAND_WORD); + commandFormatList.add(HistoryCommand.COMMAND_WORD); + commandFormatList.add(ListCommand.COMMAND_WORD); + commandFormatList.add(MeetCommand.COMMAND_WORD); + commandFormatList.add(OngoingGoalCommand.COMMAND_WORD); + commandFormatList.add(RateCommand.COMMAND_WORD); + commandFormatList.add(RedoCommand.COMMAND_WORD); + commandFormatList.add(SelectCommand.COMMAND_WORD); + commandFormatList.add(SortCommand.COMMAND_WORD); + commandFormatList.add(SortGoalCommand.COMMAND_WORD); + commandFormatList.add(ShowLofCommand.COMMAND_WORD); + commandFormatList.add(ThemeCommand.COMMAND_WORD); + commandFormatList.add(UndoCommand.COMMAND_WORD); + + //sorting the commandFormatList + Collections.sort(commandFormatList); + } +} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 8b34b862039a..27ec69182278 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -4,7 +4,9 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.goal.Goal; import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; /** * API of the Logic component @@ -22,6 +24,12 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of reminders */ + ObservableList getFilteredReminderList(); + /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ ListElementPointer getHistorySnapshot(); + + /** Returns an unmodifiable view of the filtered list of persons */ + ObservableList getFilteredGoalList(); } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9f6846bdfc74..04ad6fdaec43 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -11,7 +11,9 @@ import seedu.address.logic.parser.AddressBookParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; +import seedu.address.model.goal.Goal; import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; /** * The main LogicManager of the app. @@ -50,8 +52,18 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public ObservableList getFilteredReminderList() { + return model.getFilteredReminderList(); + } + @Override public ListElementPointer getHistorySnapshot() { return new ListElementPointer(history.getHistory()); } + + @Override + public ObservableList getFilteredGoalList() { + return model.getFilteredGoalList(); + } } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index c334710c0ea3..9e364f12a89a 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -1,37 +1,53 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -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_BIRTHDAY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CCA; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LEVEL_OF_FRIENDSHIP; 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.PREFIX_UNIT_NUMBER; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; /** - * Adds a person to the address book. + * Adds a person to CollegeZone. */ public class AddCommand extends UndoableCommand { public static final String COMMAND_WORD = "add"; + public static final String COMMAND_ALIAS = "a"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + "Parameters: " + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_BIRTHDAY + "BIRTHDAY " + + PREFIX_LEVEL_OF_FRIENDSHIP + "LEVEL OF FRIENDSHIP " + + PREFIX_UNIT_NUMBER + "UNIT NUMBER " + + PREFIX_CCA + "CCA " + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe " + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_BIRTHDAY + "24/05/1997 " + + PREFIX_LEVEL_OF_FRIENDSHIP + "9 " + + PREFIX_UNIT_NUMBER + "#02-25 " + + PREFIX_CCA + "Badminton " + + PREFIX_CCA + "tennis " + PREFIX_TAG + "friends " + PREFIX_TAG + "owesMoney"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " " + + PREFIX_NAME + " " + + PREFIX_PHONE + " " + + PREFIX_BIRTHDAY + " " + + PREFIX_LEVEL_OF_FRIENDSHIP + " " + + PREFIX_UNIT_NUMBER + " " + + PREFIX_CCA + " " + + PREFIX_TAG + " "; public static final String MESSAGE_SUCCESS = "New person added: %1$s"; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; diff --git a/src/main/java/seedu/address/logic/commands/AddGoalCommand.java b/src/main/java/seedu/address/logic/commands/AddGoalCommand.java new file mode 100644 index 000000000000..2013a9830682 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddGoalCommand.java @@ -0,0 +1,62 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GOAL_TEXT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_IMPORTANCE; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.goal.Goal; +import seedu.address.model.goal.exceptions.DuplicateGoalException; + +//@@author deborahlow97 +/** + * Adds a goal to CollegeZone. + */ +public class AddGoalCommand extends UndoableCommand { + public static final String COMMAND_WORD = "+goal"; + public static final String COMMAND_ALIAS_1 = "+g"; + public static final String COMMAND_ALIAS_2 = "addgoal"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " " + + PREFIX_IMPORTANCE + " " + + PREFIX_GOAL_TEXT + " "; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a goal to Goals Page. \n" + + "Parameters: " + + PREFIX_IMPORTANCE + "IMPORTANCE " + + PREFIX_GOAL_TEXT + "TEXT \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_IMPORTANCE + "3 " + + PREFIX_GOAL_TEXT + "lose weight \n"; + + public static final String MESSAGE_SUCCESS = "New goal added: %1$s"; + public static final String MESSAGE_DUPLICATE_GOAL = "This goal already exists in the Goals Page"; + + private final Goal toAdd; + + /** + * Creates an AddGoalCommand to add the specified {@code Goal} + */ + public AddGoalCommand(Goal goal) { + requireNonNull(goal); + toAdd = goal; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + model.addGoal(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicateGoalException e) { + throw new CommandException(MESSAGE_DUPLICATE_GOAL); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddGoalCommand // instanceof handles nulls + && toAdd.equals(((AddGoalCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddReminderCommand.java b/src/main/java/seedu/address/logic/commands/AddReminderCommand.java new file mode 100644 index 000000000000..b773ed0658de --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddReminderCommand.java @@ -0,0 +1,64 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMINDER_TEXT; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.reminder.exceptions.DuplicateReminderException; + +//@@author fuadsahmawi +/** + * Adds a reminder to the Calendar. + */ +public class AddReminderCommand extends UndoableCommand { + public static final String COMMAND_WORD = "+reminder"; + public static final String COMMAND_ALIAS = "+r"; + public static final String COMMAND_ALIAS_2 = "addreminder"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a reminder to Calendar.\n" + + "Parameters: " + + PREFIX_REMINDER_TEXT + "TEXT " + + PREFIX_DATE + "START_DATETIME " + + PREFIX_END_DATE + "END_DATETIME\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_REMINDER_TEXT + " do homework " + + PREFIX_DATE + " tonight 8pm " + + PREFIX_END_DATE + " tonight 10pm"; + + public static final String MESSAGE_SUCCESS = "New reminder added: %1$s\n" + + "Disclaimer: If date & time parsed wrongly, delete the reminder and refer to User Guide for correct" + + " format of date and time"; + + public static final String MESSAGE_DUPLICATE_REMINDER = "This reminder already exists in the Calendar"; + + private final Reminder toAdd; + + /** + * Creates an AddReminderCommand to add the specified {@code Reminder} + */ + public AddReminderCommand(Reminder reminder) { + requireNonNull(reminder); + toAdd = reminder; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + model.addReminder(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicateReminderException e) { + throw new CommandException(MESSAGE_DUPLICATE_REMINDER); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddReminderCommand // instanceof handles nulls + && toAdd.equals(((AddReminderCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index ceeb7ba913c6..48ca41ff719c 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -5,12 +5,13 @@ import seedu.address.model.AddressBook; /** - * Clears the address book. + * Clears CollegeZone. */ public class ClearCommand extends UndoableCommand { public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; + public static final String COMMAND_ALIAS = "c"; + public static final String MESSAGE_SUCCESS = "CollegeZone has been cleared!"; @Override diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 6580e0b51c90..60b1a1fecbd1 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -24,6 +24,16 @@ public static String getMessageForPersonListShownSummary(int displaySize) { return String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, displaySize); } + /** + * Constructs a feedback message to summarise an operation that displayed a listing of Resident Assistants (RA). + * + * @param displaySize used to generate summary + * @return summary message for RA displayed + */ + public static String getMessageForRaShownSummary(int displaySize) { + return String.format(Messages.MESSAGE_RA_LISTED_OVERVIEW, displaySize); + } + /** * Executes the command and returns the result message. * diff --git a/src/main/java/seedu/address/logic/commands/CompleteGoalCommand.java b/src/main/java/seedu/address/logic/commands/CompleteGoalCommand.java new file mode 100644 index 000000000000..e8e6b617e8be --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CompleteGoalCommand.java @@ -0,0 +1,172 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GOALS; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.goal.Completion; +import seedu.address.model.goal.EndDateTime; +import seedu.address.model.goal.Goal; + +import seedu.address.model.goal.GoalText; +import seedu.address.model.goal.Importance; +import seedu.address.model.goal.StartDateTime; +import seedu.address.model.goal.exceptions.GoalNotFoundException; + +//@@author deborahlow97 +/** + * Edits the details of an existing goal in CollegeZone. + */ +public class CompleteGoalCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "!goal"; + public static final String COMMAND_ALIAS_1 = "!g"; + public static final String COMMAND_ALIAS_2 = "completegoal"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Indicate completion of the goal identified " + + "by the index number used in the last goal listing.\n " + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 "; + + public static final String MESSAGE_COMPLETE_GOAL_SUCCESS = "Completed Goal! : %1$s"; + + private final Index index; + private final CompleteGoalDescriptor completeGoalDescriptor; + + private Goal goalToUpdate; + private Goal updatedGoal; + + /** + * @param index of the goal in the filtered goal list to update + */ + public CompleteGoalCommand(Index index, CompleteGoalDescriptor completeGoalDescriptor) { + requireNonNull(index); + requireNonNull(completeGoalDescriptor); + + this.index = index; + this.completeGoalDescriptor = new CompleteGoalDescriptor(completeGoalDescriptor); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.updateGoalWithoutParameters(goalToUpdate, updatedGoal); + } catch (GoalNotFoundException pnfe) { + throw new AssertionError("The target goal cannot be missing"); + } + model.updateFilteredGoalList(PREDICATE_SHOW_ALL_GOALS); + return new CommandResult(String.format(MESSAGE_COMPLETE_GOAL_SUCCESS, updatedGoal)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredGoalList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + } + + goalToUpdate = lastShownList.get(index.getZeroBased()); + if (goalToUpdate.getCompletion().hasCompleted) { + throw new CommandException(Messages.MESSAGE_GOAL_COMPLETED_ERROR); + } + updatedGoal = createUpdatedGoal(goalToUpdate, completeGoalDescriptor); + } + + /** + * Creates and returns a {@code Goal} with the details of {@code goalToUpdate} + * edited with {@code completeGoalDescriptor}. + */ + private static Goal createUpdatedGoal(Goal goalToUpdate, CompleteGoalDescriptor completeGoalDescriptor) { + assert goalToUpdate != null; + + GoalText goalText = goalToUpdate.getGoalText(); + Importance importance = goalToUpdate.getImportance(); + StartDateTime startDateTime = goalToUpdate.getStartDateTime(); + EndDateTime updatedEndDateTime = completeGoalDescriptor.getEndDateTime() + .orElse(goalToUpdate.getEndDateTime()); + Completion updatedCompletion = completeGoalDescriptor.getCompletion().orElse(goalToUpdate.getCompletion()); + + return new Goal(importance, goalText, startDateTime, updatedEndDateTime, updatedCompletion); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof CompleteGoalCommand)) { + return false; + } + + // state check + CompleteGoalCommand e = (CompleteGoalCommand) other; + return index.equals(e.index) + && completeGoalDescriptor.equals(e.completeGoalDescriptor) + && Objects.equals(goalToUpdate, e.goalToUpdate); + } + + /** + * Stores the details to update the goal with. + */ + public static class CompleteGoalDescriptor { + + private EndDateTime endDateTime; + private Completion completion; + + public CompleteGoalDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code toCopy} is used internally. + */ + public CompleteGoalDescriptor(CompleteGoalDescriptor toCopy) { + setEndDateTime(toCopy.endDateTime); + setCompletion(toCopy.completion); + } + + public void setEndDateTime(EndDateTime endDateTime) { + this.endDateTime = endDateTime; + } + + public Optional getEndDateTime() { + return Optional.ofNullable(endDateTime); + } + + public void setCompletion(Completion completion) { + this.completion = completion; + } + + public Optional getCompletion() { + return Optional.ofNullable(completion); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof CompleteGoalDescriptor)) { + return false; + } + + // state check + CompleteGoalDescriptor e = (CompleteGoalDescriptor) other; + + return getEndDateTime().equals(e.getEndDateTime()) + && getCompletion().equals(e.getCompletion()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index b539d240001a..a91799aecd12 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -12,12 +12,14 @@ import seedu.address.model.person.exceptions.PersonNotFoundException; /** - * Deletes a person identified using it's last displayed index from the address book. + * Deletes a person identified using it's last displayed index from CollegeZone. */ public class DeleteCommand extends UndoableCommand { public static final String COMMAND_WORD = "delete"; + public static final String COMMAND_ALIAS = "d"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes the person identified by the index number used in the last person listing.\n" + "Parameters: INDEX (must be a positive integer)\n" diff --git a/src/main/java/seedu/address/logic/commands/DeleteGoalCommand.java b/src/main/java/seedu/address/logic/commands/DeleteGoalCommand.java new file mode 100644 index 000000000000..9a5df5c87a07 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteGoalCommand.java @@ -0,0 +1,71 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.goal.Goal; +import seedu.address.model.goal.exceptions.GoalNotFoundException; + +//@@author deborahlow97 +/** + * Deletes a goal identified using it's last displayed index from CollegeZone. + */ +public class DeleteGoalCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "-goal"; + + public static final String COMMAND_ALIAS_1 = "-g"; + + public static final String COMMAND_ALIAS_2 = "deletegoal"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the goal identified by the index number used in the goal listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_GOAL_SUCCESS = "Deleted Goal: %1$s"; + + private final Index targetIndex; + + private Goal goalToDelete; + + public DeleteGoalCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(goalToDelete); + try { + model.deleteGoal(goalToDelete); + } catch (GoalNotFoundException pnfe) { + throw new AssertionError("The target goal cannot be missing"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_GOAL_SUCCESS, goalToDelete)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredGoalList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + } + + goalToDelete = lastShownList.get(targetIndex.getZeroBased()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteGoalCommand // instanceof handles nulls + && this.targetIndex.equals(((DeleteGoalCommand) other).targetIndex) // state check + && Objects.equals(this.goalToDelete, ((DeleteGoalCommand) other).goalToDelete)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteMeetCommand.java b/src/main/java/seedu/address/logic/commands/DeleteMeetCommand.java new file mode 100644 index 000000000000..770f1c4f307d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteMeetCommand.java @@ -0,0 +1,71 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.PersonNotFoundException; + +//@@author sham-sheer +/** + * Removes the meet up set with a person using the person's displayed index from CollegeZone. + */ +public class DeleteMeetCommand extends UndoableCommand { + public static final String COMMAND_WORD = "-meet"; + + public static final String COMMAND_ALIAS = "-m"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the person's meet date identified by the index number used in the last person listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "You are not meeting %1$s anymore. "; + + private final Index targetIndex; + + private Person personToDelete; + + public DeleteMeetCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(personToDelete); + try { + model.deleteMeetDate(personToDelete); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target person cannot be missing"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + personToDelete = lastShownList.get(targetIndex.getZeroBased()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteMeetCommand // instanceof handles nulls + && this.targetIndex.equals(((DeleteMeetCommand) other).targetIndex) // state check + && Objects.equals(this.personToDelete, ((DeleteMeetCommand) other).personToDelete)); + } + + +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteReminderCommand.java b/src/main/java/seedu/address/logic/commands/DeleteReminderCommand.java new file mode 100644 index 000000000000..0df79d8130b1 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteReminderCommand.java @@ -0,0 +1,74 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.reminder.ReminderTextPredicate; +import seedu.address.model.reminder.exceptions.ReminderNotFoundException; + +//@@author fuadsahmawi +/** + * Deletes a reminder identified using its title in the calendar + */ +public class DeleteReminderCommand extends UndoableCommand { + public static final String COMMAND_WORD = "-reminder"; + public static final String COMMAND_ALIAS = "-r"; + public static final String COMMAND_ALIAS_2 = "deletereminder"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the reminder identified by its title & start time in the calendar.\n" + + "Parameters: REMINDER_TITLE & START_DATETIME\n" + + "Example: " + COMMAND_WORD + " text/Eat pills d/tmr 8pm"; + + public static final String MESSAGE_DELETE_REMINDER_SUCCESS = "Deleted Reminder: %1$s"; + + private Index targetIndex; + + private String dateTime; + + private ReminderTextPredicate predicate; + + private Reminder reminderToDelete; + + public DeleteReminderCommand(ReminderTextPredicate predicate, String dateTime) { + this.predicate = predicate; + this.dateTime = dateTime; + } + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(reminderToDelete); + try { + model.deleteReminder(reminderToDelete); + } catch (ReminderNotFoundException pnfe) { + throw new AssertionError("The target reminder cannot be missing"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_REMINDER_SUCCESS, reminderToDelete)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + model.updateFilteredReminderList(predicate); + List lastShownList = model.getFilteredReminderList(); + targetIndex = Index.fromOneBased(1); + if (lastShownList.size() > 1) { + for (Reminder reminder : lastShownList) { + if (reminder.getDateTime().toString().equals(dateTime)) { + reminderToDelete = reminder; + } + } + } else { + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_REMINDER_TEXT_DATE); + } + + reminderToDelete = lastShownList.get(targetIndex.getZeroBased()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index e6c3a3e034bc..7d8fb7a77aa7 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -1,11 +1,13 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -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_BIRTHDAY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CCA; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LEVEL_OF_FRIENDSHIP; 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.PREFIX_UNIT_NUMBER; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import java.util.Collections; @@ -19,34 +21,50 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.util.CollectionUtil; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; +import seedu.address.model.person.Birthday; +import seedu.address.model.person.Cca; +import seedu.address.model.person.LevelOfFriendship; +import seedu.address.model.person.Meet; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.UnitNumber; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; import seedu.address.model.tag.Tag; /** - * Edits the details of an existing person in the address book. + * Edits the details of an existing person in CollegeZone. */ public class EditCommand extends UndoableCommand { public static final String COMMAND_WORD = "edit"; + public static final String COMMAND_ALIAS = "e"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " 1" + + PREFIX_NAME + " " + + PREFIX_PHONE + " " + + PREFIX_BIRTHDAY + " " + + PREFIX_LEVEL_OF_FRIENDSHIP + " " + + PREFIX_UNIT_NUMBER + " " + + PREFIX_CCA + " " + + PREFIX_TAG + " "; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " + "by the index number used in the last person listing. " + "Existing values will be overwritten by the input values.\n" + "Parameters: INDEX (must be a positive integer) " + "[" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_BIRTHDAY + "BIRTHDAY] " + + "[" + PREFIX_LEVEL_OF_FRIENDSHIP + "LEVEL OF FRIENDSHIP] " + + "[" + PREFIX_UNIT_NUMBER + "UNIT NUMBER] " + + "[" + PREFIX_CCA + "CCA]... " + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " 1 " + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; + + PREFIX_BIRTHDAY + "21/3/1990 "; public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; @@ -104,11 +122,16 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); + Birthday updatedBirthday = editPersonDescriptor.getBirthday().orElse(personToEdit.getBirthday()); + LevelOfFriendship updatedLevelOfFriendship = editPersonDescriptor.getLevelOfFriendship() + .orElse(personToEdit.getLevelOfFriendship()); + UnitNumber updatedUnitNumber = editPersonDescriptor.getUnitNumber().orElse(personToEdit.getUnitNumber()); + Meet updatedMeetDate = personToEdit.getMeetDate(); + Set updatedCcas = editPersonDescriptor.getCcas().orElse(personToEdit.getCcas()); Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + return new Person(updatedName, updatedPhone, updatedBirthday, updatedLevelOfFriendship, updatedUnitNumber, + updatedCcas, updatedMeetDate, updatedTags); } @Override @@ -137,8 +160,10 @@ public boolean equals(Object other) { public static class EditPersonDescriptor { private Name name; private Phone phone; - private Email email; - private Address address; + private Birthday birthday; + private LevelOfFriendship levelOfFriendship; + private UnitNumber unitNumber; + private Set ccas; private Set tags; public EditPersonDescriptor() {} @@ -150,8 +175,10 @@ public EditPersonDescriptor() {} public EditPersonDescriptor(EditPersonDescriptor toCopy) { setName(toCopy.name); setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); + setBirthday(toCopy.birthday); + setLevelOfFriendship(toCopy.levelOfFriendship); + setUnitNumber(toCopy.unitNumber); + setCcas(toCopy.ccas); setTags(toCopy.tags); } @@ -159,7 +186,8 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(this.name, this.phone, this.email, this.address, this.tags); + return CollectionUtil.isAnyNonNull(this.name, this.phone, this.birthday, + this.levelOfFriendship, this.unitNumber, this.ccas, this.tags); } public void setName(Name name) { @@ -178,20 +206,46 @@ public Optional getPhone() { return Optional.ofNullable(phone); } - public void setEmail(Email email) { - this.email = email; + public void setBirthday(Birthday birthday) { + this.birthday = birthday; + } + + public Optional getBirthday() { + return Optional.ofNullable(birthday); + } + + public void setLevelOfFriendship(LevelOfFriendship levelOfFriendship) { + this.levelOfFriendship = levelOfFriendship; + } + + public Optional getLevelOfFriendship() { + return Optional.ofNullable(levelOfFriendship); } - public Optional getEmail() { - return Optional.ofNullable(email); + public void setUnitNumber(UnitNumber unitNumber) { + this.unitNumber = unitNumber; } - public void setAddress(Address address) { - this.address = address; + public Optional getUnitNumber() { + return Optional.ofNullable(unitNumber); } - public Optional
getAddress() { - return Optional.ofNullable(address); + + /** + * Sets {@code ccas} to this object's {@code ccas}. + * A defensive copy of {@code ccas} is used internally. + */ + public void setCcas(Set ccas) { + this.ccas = (ccas != null) ? new HashSet<>(ccas) : null; + } + + /** + * Returns an unmodifiable cca set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code ccas} is null. + */ + public Optional> getCcas() { + return (ccas != null) ? Optional.of(Collections.unmodifiableSet(ccas)) : Optional.empty(); } /** @@ -228,8 +282,10 @@ public boolean equals(Object other) { return getName().equals(e.getName()) && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) + && getBirthday().equals(e.getBirthday()) + && getLevelOfFriendship().equals(e.getLevelOfFriendship()) + && getUnitNumber().equals(e.getUnitNumber()) + && getCcas().equals(e.getCcas()) && getTags().equals(e.getTags()); } } diff --git a/src/main/java/seedu/address/logic/commands/EditGoalCommand.java b/src/main/java/seedu/address/logic/commands/EditGoalCommand.java new file mode 100644 index 000000000000..bccc4e9bb887 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EditGoalCommand.java @@ -0,0 +1,187 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GOAL_TEXT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_IMPORTANCE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GOALS; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.goal.Completion; +import seedu.address.model.goal.EndDateTime; +import seedu.address.model.goal.Goal; +import seedu.address.model.goal.GoalText; +import seedu.address.model.goal.Importance; +import seedu.address.model.goal.StartDateTime; +import seedu.address.model.goal.exceptions.DuplicateGoalException; +import seedu.address.model.goal.exceptions.GoalNotFoundException; + +//@@author deborahlow97 +/** + * Edits the details of an existing goal in CollegeZone. + */ +public class EditGoalCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "~goal"; + public static final String COMMAND_ALIAS_1 = "~g"; + public static final String COMMAND_ALIAS_2 = "editgoal"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the goal identified " + + "by the index number used in the last goal listing. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_GOAL_TEXT + "GOAL TEXT] " + + "[" + PREFIX_IMPORTANCE + "IMPORTANCE] \n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_IMPORTANCE + "2 "; + + public static final String MESSAGE_EDIT_GOAL_SUCCESS = "Edited Goal: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_GOAL = "This goal already exists in the address book."; + + private final Index index; + private final EditGoalDescriptor editGoalDescriptor; + + private Goal goalToEdit; + private Goal editedGoal; + + /** + * @param index of the goal in the filtered goal list to edit + * @param editGoalDescriptor details to edit the goal with + */ + public EditGoalCommand(Index index, EditGoalDescriptor editGoalDescriptor) { + requireNonNull(index); + requireNonNull(editGoalDescriptor); + + this.index = index; + this.editGoalDescriptor = new EditGoalDescriptor(editGoalDescriptor); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.updateGoal(goalToEdit, editedGoal); + } catch (DuplicateGoalException dpe) { + throw new CommandException(MESSAGE_DUPLICATE_GOAL); + } catch (GoalNotFoundException pnfe) { + throw new AssertionError("The target goal cannot be missing"); + } + model.updateFilteredGoalList(PREDICATE_SHOW_ALL_GOALS); + return new CommandResult(String.format(MESSAGE_EDIT_GOAL_SUCCESS, editedGoal)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredGoalList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + } + + goalToEdit = lastShownList.get(index.getZeroBased()); + editedGoal = createEditedGoal(goalToEdit, editGoalDescriptor); + } + + /** + * Creates and returns a {@code Goal} with the details of {@code goalToEdit} + * edited with {@code editGoalDescriptor}. + */ + private static Goal createEditedGoal(Goal goalToEdit, EditGoalDescriptor editGoalDescriptor) { + assert goalToEdit != null; + + GoalText updatedGoalText = editGoalDescriptor.getGoalText().orElse(goalToEdit.getGoalText()); + Importance updatedImportance = editGoalDescriptor.getImportance().orElse(goalToEdit.getImportance()); + StartDateTime startDateTime = goalToEdit.getStartDateTime(); + EndDateTime endDateTime = goalToEdit.getEndDateTime(); + Completion completion = goalToEdit.getCompletion(); + return new Goal(updatedImportance, updatedGoalText, startDateTime, endDateTime, completion); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditGoalCommand)) { + return false; + } + + // state check + EditGoalCommand e = (EditGoalCommand) other; + return index.equals(e.index) + && editGoalDescriptor.equals(e.editGoalDescriptor) + && Objects.equals(goalToEdit, e.goalToEdit); + } + + /** + * Stores the details to edit the goal with. Each non-empty field value will replace the + * corresponding field value of the goal. + */ + public static class EditGoalDescriptor { + private GoalText goalText; + private Importance importance; + + public EditGoalDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tags} is used internally. + */ + public EditGoalDescriptor(EditGoalDescriptor toCopy) { + setGoalText(toCopy.goalText); + setImportance(toCopy.importance); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(this.goalText, this.importance); + } + + public void setGoalText(GoalText goalText) { + this.goalText = goalText; + } + + public Optional getGoalText() { + return Optional.ofNullable(goalText); + } + + public void setImportance(Importance importance) { + this.importance = importance; + } + + public Optional getImportance() { + return Optional.ofNullable(importance); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditGoalDescriptor)) { + return false; + } + + // state check + EditGoalDescriptor e = (EditGoalDescriptor) other; + + return getGoalText().equals(e.getGoalText()) + && getImportance().equals(e.getImportance()); + } + } +} + diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index b1e671f633d2..affd0770bb32 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -1,36 +1,114 @@ package seedu.address.logic.commands; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BIRTHDAY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CCA; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LEVEL_OF_FRIENDSHIP; +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.PREFIX_UNIT_NUMBER; + +import seedu.address.commons.util.CollectionUtil; import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.TagContainsKeywordsPredicate; + + + +//@@author fuadsahmawi /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Finds and lists all persons in CollegeZone whose name contains any of the argument keywords. * Keyword matching is case sensitive. */ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + public static final String COMMAND_ALIAS = "f"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " 1" + + PREFIX_NAME + " " + + PREFIX_PHONE + " " + + PREFIX_BIRTHDAY + " " + + PREFIX_LEVEL_OF_FRIENDSHIP + " " + + PREFIX_UNIT_NUMBER + " " + + PREFIX_CCA + " " + + PREFIX_TAG + " "; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names or tags contain any of " + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; + + "Parameters: n/KEYWORD [MORE_KEYWORDS]... or t/KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " n/alice bob charlie"; + + public static final String MESSAGE_NOT_EDITED = "A keyword to find name or tag must be provided."; - private final NameContainsKeywordsPredicate predicate; + private TagContainsKeywordsPredicate predicateT = null; + private NameContainsKeywordsPredicate predicateN = null; + + public FindCommand(NameContainsKeywordsPredicate predicateName) { + this.predicateN = predicateName; + } - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; + public FindCommand(TagContainsKeywordsPredicate predicate) { + this.predicateT = predicate; } @Override public CommandResult execute() { - model.updateFilteredPersonList(predicate); + if (predicateT == null) { + model.updateFilteredPersonList(predicateN); + } else { + model.updateFilteredPersonList(predicateT); + } return new CommandResult(getMessageForPersonListShownSummary(model.getFilteredPersonList().size())); } @Override public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && this.predicate.equals(((FindCommand) other).predicate)); // state check + if (this.predicateT == null) { + return other == this // short circuit if same object + || (other instanceof FindCommand // instanceof handles nulls + && this.predicateN.equals(((FindCommand) other).predicateN)); // state check + } else { + return other == this // short circuit if same object + || (other instanceof FindCommand // instanceof handles nulls + && this.predicateT.equals(((FindCommand) other).predicateT)); // state check + } + } + + /** + * Stores the details to edit the person with. Each non-empty field value will replace the + * corresponding field value of the person. + */ + public static class FindPersonDescriptor { + private String[] nameKeywords; + private String[] tagKeywords; + + public FindPersonDescriptor() { + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(this.nameKeywords, this.tagKeywords); + } + + public void setNameKeywords(String name) { + this.nameKeywords = name.split("\\s+"); + ; + } + + public void setTagKeywords(String tags) { + this.tagKeywords = tags.split("\\s+"); + } + + public String[] getNameKeywords() { + return this.nameKeywords; + } + + public String[] getTagKeyWords() { + return this.tagKeywords; + } } } diff --git a/src/main/java/seedu/address/logic/commands/HistoryCommand.java b/src/main/java/seedu/address/logic/commands/HistoryCommand.java index f87abee5511d..a3a8f11563d0 100644 --- a/src/main/java/seedu/address/logic/commands/HistoryCommand.java +++ b/src/main/java/seedu/address/logic/commands/HistoryCommand.java @@ -15,6 +15,7 @@ public class HistoryCommand extends Command { public static final String COMMAND_WORD = "history"; + public static final String COMMAND_ALIAS = "h"; public static final String MESSAGE_SUCCESS = "Entered commands (from most recent to earliest):\n%1$s"; public static final String MESSAGE_NO_HISTORY = "You have not yet entered any commands."; diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 7b6463780824..045125bace6b 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -3,12 +3,14 @@ import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; /** - * Lists all persons in the address book to the user. + * Lists all persons in CollegeZone to the user. */ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; + public static final String COMMAND_ALIAS = "l"; + public static final String MESSAGE_SUCCESS = "Listed all persons"; diff --git a/src/main/java/seedu/address/logic/commands/MeetCommand.java b/src/main/java/seedu/address/logic/commands/MeetCommand.java new file mode 100644 index 000000000000..26baacb34e62 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/MeetCommand.java @@ -0,0 +1,119 @@ + +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.person.Meet; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.person.exceptions.PersonNotFoundException; + +//@@author sham-sheer +/** + * Adds a meeting to CollegeZone. + */ +public class MeetCommand extends UndoableCommand { + public static final String COMMAND_WORD = "meet"; + public static final String COMMAND_ALIAS = "m"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds the date of meetup for the person identified " + + "by the index number used in the last person listing. " + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_DATE + "[REMARK]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_DATE + "01/04/2018"; + + + public static final String MESSAGE_ADD_MEETDATE_SUCCESS = "%1$s added for meet up! Check out your Calendar!"; + public static final String MESSAGE_DELETE_MEETDATE_SUCCESS = "You are not meeting %1$s anymore!!"; + public static final String MESSAGE_DUPLICATE_PERSON = "This person has already been set to have meeting."; + + private final Index targetIndex; + private final Meet date; + + private Person personToEdit; + private Person editedPerson; + + /** + * @param targetIndex of the person in the filtered person list you want to meet + * @param date you want to meet the person + */ + + public MeetCommand(Index targetIndex, Meet date) { + requireNonNull(targetIndex); + requireNonNull(date); + + this.targetIndex = targetIndex; + this.date = date; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(personToEdit); + requireNonNull(editedPerson); + + try { + model.updatePerson(personToEdit, editedPerson); + } catch (DuplicatePersonException dpe) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } catch (PersonNotFoundException pnfe) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + return new CommandResult(generateSuccessMessage(editedPerson)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + personToEdit = lastShownList.get(targetIndex.getZeroBased()); + editedPerson = new Person(personToEdit.getName(), personToEdit.getPhone(), personToEdit.getBirthday(), + personToEdit.getLevelOfFriendship(), personToEdit.getUnitNumber(), personToEdit.getCcas(), + date, personToEdit.getTags()); + } + + /** + * Generates a command execution success message based on whether the remark is added to or removed from + * {@code personToEdit}. + */ + private String generateSuccessMessage(Person personToEdit) { + String message = !date.value.isEmpty() ? MESSAGE_ADD_MEETDATE_SUCCESS : MESSAGE_DELETE_MEETDATE_SUCCESS; + return String.format(message, personToEdit.getName()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof MeetCommand)) { + return false; + } + + // state check + MeetCommand e = (MeetCommand) other; + return targetIndex.equals(e.targetIndex) + && date.equals(e.date); + } + + + + + +} diff --git a/src/main/java/seedu/address/logic/commands/OngoingGoalCommand.java b/src/main/java/seedu/address/logic/commands/OngoingGoalCommand.java new file mode 100644 index 000000000000..886ba66211f2 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/OngoingGoalCommand.java @@ -0,0 +1,189 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GOALS; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.goal.Completion; +import seedu.address.model.goal.EndDateTime; +import seedu.address.model.goal.Goal; + +import seedu.address.model.goal.GoalText; +import seedu.address.model.goal.Importance; +import seedu.address.model.goal.StartDateTime; +import seedu.address.model.goal.exceptions.GoalNotFoundException; + +//@@author deborahlow97 +/** + * Edits the details of an existing goal in CollegeZone. + */ +public class OngoingGoalCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "-!goal"; + public static final String COMMAND_ALIAS_1 = "-!g"; + public static final String COMMAND_ALIAS_2 = "ongoinggoal"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Indicate identified goal is not completed " + + "and still ongoing.\n" + + "Goal is identified " + + "by the index number used in the last goal listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 "; + + public static final String MESSAGE_ONGOING_GOAL_SUCCESS = "Ongoing Goal! : %1$s"; + + private final Index index; + private final OngoingGoalDescriptor ongoingGoalDescriptor; + + private Goal goalToUpdate; + private Goal updatedGoal; + + /** + * @param index of the goal in the filtered goal list to update + */ + public OngoingGoalCommand(Index index, OngoingGoalDescriptor ongoingGoalDescriptor) { + requireNonNull(index); + requireNonNull(ongoingGoalDescriptor); + + this.index = index; + this.ongoingGoalDescriptor = new OngoingGoalDescriptor(ongoingGoalDescriptor); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.updateGoalWithoutParameters(goalToUpdate, updatedGoal); + } catch (GoalNotFoundException pnfe) { + throw new AssertionError("The target goal cannot be missing"); + } + model.updateFilteredGoalList(PREDICATE_SHOW_ALL_GOALS); + return new CommandResult(String.format(MESSAGE_ONGOING_GOAL_SUCCESS, updatedGoal)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredGoalList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_GOAL_DISPLAYED_INDEX); + } + + goalToUpdate = lastShownList.get(index.getZeroBased()); + if (!goalToUpdate.getCompletion().hasCompleted) { + throw new CommandException(Messages.MESSAGE_GOAL_ONGOING_ERROR); + } + updatedGoal = createUpdatedGoal(goalToUpdate, ongoingGoalDescriptor); + } + + /** + * Creates and returns a {@code Goal} with the details of {@code goalToUpdate} + * edited with {@code ongoingGoalDescriptor}. + */ + private static Goal createUpdatedGoal(Goal goalToUpdate, OngoingGoalDescriptor ongoingGoalDescriptor) { + assert goalToUpdate != null; + + GoalText goalText = ongoingGoalDescriptor.getGoalText().orElse(goalToUpdate.getGoalText()); + Importance importance = ongoingGoalDescriptor.getImportance().orElse(goalToUpdate.getImportance()); + StartDateTime startDateTime = ongoingGoalDescriptor.getStartDateTime().orElse(goalToUpdate.getStartDateTime()); + EndDateTime updatedEndDateTime = ongoingGoalDescriptor.getEndDateTime() + .orElse(goalToUpdate.getEndDateTime()); + Completion updatedCompletion = ongoingGoalDescriptor.getCompletion().orElse(goalToUpdate.getCompletion()); + + return new Goal(importance, goalText, startDateTime, updatedEndDateTime, updatedCompletion); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof OngoingGoalCommand)) { + return false; + } + + // state check + OngoingGoalCommand e = (OngoingGoalCommand) other; + return index.equals(e.index) + && ongoingGoalDescriptor.equals(e.ongoingGoalDescriptor) + && Objects.equals(goalToUpdate, e.goalToUpdate); + } + + /** + * Stores the details to update the goal with. + */ + public static class OngoingGoalDescriptor { + private GoalText goalText; + private Importance importance; + private StartDateTime startDateTime; + private EndDateTime endDateTime; + private Completion completion; + + public OngoingGoalDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code toCopy} is used internally. + */ + public OngoingGoalDescriptor(OngoingGoalDescriptor toCopy) { + setEndDateTime(toCopy.endDateTime); + setCompletion(toCopy.completion); + } + + public void setEndDateTime(EndDateTime endDateTime) { + this.endDateTime = endDateTime; + } + + public Optional getEndDateTime() { + return Optional.ofNullable(endDateTime); + } + + public void setCompletion(Completion completion) { + this.completion = completion; + } + + public Optional getCompletion() { + return Optional.ofNullable(completion); + } + + public Optional getStartDateTime() { + return Optional.ofNullable(startDateTime); + } + + public Optional getImportance() { + return Optional.ofNullable(importance); + } + public Optional getGoalText() { + return Optional.ofNullable(goalText); + } + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof OngoingGoalDescriptor)) { + return false; + } + + // state check + OngoingGoalDescriptor e = (OngoingGoalDescriptor) other; + + return getGoalText().equals(e.getGoalText()) + && getImportance().equals(e.getImportance()) + && getStartDateTime().equals(e.getStartDateTime()) + && getEndDateTime().equals(e.getEndDateTime()) + && getCompletion().equals(e.getCompletion()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/RateCommand.java b/src/main/java/seedu/address/logic/commands/RateCommand.java new file mode 100644 index 000000000000..cb433065da3f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RateCommand.java @@ -0,0 +1,91 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LEVEL_OF_FRIENDSHIP; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; + +import seedu.address.commons.core.index.Index; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.person.LevelOfFriendship; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.person.exceptions.PersonNotFoundException; + +//@@author zuweitrack +/** + * Rates existing person(s) in CollegeZone. + */ +public class RateCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "rate"; + + public static final String COMMAND_ALIAS = "rt"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Rates person(s) in College Zone " + + "and changes the level of friendship " + + "by the index number used in the latest listing.\n" + + "Existing level of friendship will be overwritten by the input values.\n" + + "Parameters: INDEX(s) (must be a positive integer) " + + "[" + PREFIX_LEVEL_OF_FRIENDSHIP + "LEVELOFFRIENDSHIP] (between 1 and 10)\n" + + "Example: " + COMMAND_WORD + " 1 3 " + + PREFIX_LEVEL_OF_FRIENDSHIP + "5 "; + + private static final String MESSAGE_EDIT_PERSON_SUCCESS = "Rated person(s) successfully"; + private static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + private static final String MESSAGE_PERSON_NOT_FOUND = "The selected person cannot be missing"; + private static final String MESSAGE_ONE_OR_MORE_INVALID_INDEX = + "One or more index inputs may not be valid" + + " and only the person(s) of valid indexes are being rated!"; + + private final List indexList; + private final String levelOfFriendship; + + /** + * @param indexList list of index(es) of the person in the filtered person list + * @param levelOfFriendship new level of friendship to add to the person + */ + public RateCommand(List indexList, String levelOfFriendship) { + requireNonNull(indexList); + requireNonNull(levelOfFriendship); + + this.indexList = indexList; + this.levelOfFriendship = levelOfFriendship; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + List latestList = model.getFilteredPersonList(); + for (Index index : indexList) { + + if (index.getZeroBased() >= latestList.size()) { + throw new CommandException(MESSAGE_ONE_OR_MORE_INVALID_INDEX); + } + + Person selectedPerson = latestList.get(index.getZeroBased()); + + try { + Person editedPerson = new Person(selectedPerson.getName(), selectedPerson.getPhone(), + selectedPerson.getBirthday(), new LevelOfFriendship(levelOfFriendship), + selectedPerson.getUnitNumber(), + selectedPerson.getCcas(), selectedPerson.getMeetDate(), selectedPerson.getTags()); + model.updatePerson(selectedPerson, editedPerson); + + } catch (PersonNotFoundException pnfe) { + throw new CommandException(MESSAGE_PERSON_NOT_FOUND); + } catch (DuplicatePersonException dpe) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + + } + + } + + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + return new CommandResult(MESSAGE_EDIT_PERSON_SUCCESS); + + } + +} diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java index 7b99d0f372fc..311c95c1a1f2 100644 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java @@ -13,6 +13,7 @@ public class RedoCommand extends Command { public static final String COMMAND_WORD = "redo"; + public static final String COMMAND_ALIAS = "r"; public static final String MESSAGE_SUCCESS = "Redo success!"; public static final String MESSAGE_FAILURE = "No more commands to redo!"; diff --git a/src/main/java/seedu/address/logic/commands/SeekRaCommand.java b/src/main/java/seedu/address/logic/commands/SeekRaCommand.java new file mode 100644 index 000000000000..f70ef4449bc2 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SeekRaCommand.java @@ -0,0 +1,42 @@ +package seedu.address.logic.commands; + +import seedu.address.model.person.UnitNumberContainsKeywordsPredicate; + +//@@author zuweitrack +/** + * Finds and lists the Resident Assistant (RA) of an individual RC Student + * in address book whose name contains any of the argument keywords. + * Keyword matching is case sensitive. + */ +public class SeekRaCommand extends Command { + + public static final String COMMAND_WORD = "seek"; + + public static final String COMMAND_ALIAS = "sk"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Seeks and lists all Resident Assistants (RA) of RC4 with the" + + " individual RC student whose name contain any of " + + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " alice bob charlie"; + + private final UnitNumberContainsKeywordsPredicate predicate; + + public SeekRaCommand(UnitNumberContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute() { + model.updateFilteredPersonList(predicate); + return new CommandResult(getMessageForRaShownSummary(model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SeekRaCommand // instanceof handles nulls + && this.predicate.equals(((SeekRaCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java index 9e3840a9dde6..ffba68da2cac 100644 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ b/src/main/java/seedu/address/logic/commands/SelectCommand.java @@ -10,12 +10,14 @@ import seedu.address.model.person.Person; /** - * Selects a person identified using it's last displayed index from the address book. + * Selects a person identified using it's last displayed index from CollegeZone. */ public class SelectCommand extends Command { public static final String COMMAND_WORD = "select"; + public static final String COMMAND_ALIAS = "s"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Selects the person identified by the index number used in the last person listing.\n" + "Parameters: INDEX (must be a positive integer)\n" diff --git a/src/main/java/seedu/address/logic/commands/ShowLofCommand.java b/src/main/java/seedu/address/logic/commands/ShowLofCommand.java new file mode 100644 index 000000000000..b8803c9964ff --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ShowLofCommand.java @@ -0,0 +1,42 @@ +package seedu.address.logic.commands; + +import seedu.address.model.person.LofContainsValuePredicate; + +//@@author zuweitrack +/** + * Finds and lists the person(s) + * in address book whose level of friendship matches the input value + * of the argument keywords. + */ +public class ShowLofCommand extends Command { + + public static final String COMMAND_WORD = "show"; + + public static final String COMMAND_ALIAS = "sh"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Shows person(s) in CollegeZone with the" + + " whose level of friendship contains any of " + + "specified level and displays them as a list with index numbers.\n" + + "Parameters: LEVELOFFRIENDSHIP [MORE_LEVELOFFRIENDSHIP]...\n" + + "Example: " + COMMAND_WORD + " 1 2 7"; + + private final LofContainsValuePredicate predicate; + + public ShowLofCommand(LofContainsValuePredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute() { + model.updateFilteredPersonList(predicate); + return new CommandResult(getMessageForPersonListShownSummary(model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ShowLofCommand // instanceof handles nulls + && this.predicate.equals(((ShowLofCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/SortCommand.java b/src/main/java/seedu/address/logic/commands/SortCommand.java new file mode 100644 index 000000000000..9d81de9935f2 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SortCommand.java @@ -0,0 +1,84 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.person.Person; + + +//@@author sham-sheer +/** + * Sort the persons in CollegeZone based on the users parameters + */ +public class SortCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "sort"; + + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid sort type: %1$s"; + + public static final String MESSAGE_EMPTY_LIST = "CollegeZone student list is empty, There is nothing to sort!"; + + public static final String MESSAGE_SORTED_SUCCESS_LEVEL_OF_FRIENDSHIP = "List sorted according to Friendship lvl!"; + + public static final String MESSAGE_SORTED_SUCCESS_MEET_DATE = "List sorted according to your latest meet date!"; + + public static final String MESSAGE_SORTED_SUCCESS_BIRTHDAY = "List sorted according to show latest birthday!"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sorts the person list identified by the index number used in the last person listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + private final Index index; + + private final ObservableList internalList = FXCollections.observableArrayList(); + + public SortCommand(Index index) { + requireNonNull(index); + this.index = index; + } + + + @Override + public CommandResult executeUndoableCommand() { + try { + model.sortPersons(index); + } catch (IndexOutOfBoundsException ioe) { + throw new AssertionError("The index is out of bounds"); + } + if (index.getOneBased() == 1) { + return new CommandResult(String.format(MESSAGE_SORTED_SUCCESS_LEVEL_OF_FRIENDSHIP)); + } + if (index.getOneBased() == 2) { + return new CommandResult(String.format(MESSAGE_SORTED_SUCCESS_MEET_DATE)); + } + return new CommandResult(String.format(MESSAGE_SORTED_SUCCESS_BIRTHDAY)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (index.getOneBased() > 3) { + throw new CommandException(String.format(SortCommand.MESSAGE_INVALID_COMMAND_FORMAT, index.getOneBased())); + } + if (lastShownList.size() == 0) { + throw new CommandException(String.format(SortCommand.MESSAGE_EMPTY_LIST)); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortCommand // instanceof handles nulls + && this.index.equals(((SortCommand) other).index)); // state check + } + + +} + diff --git a/src/main/java/seedu/address/logic/commands/SortGoalCommand.java b/src/main/java/seedu/address/logic/commands/SortGoalCommand.java new file mode 100644 index 000000000000..1597a5a0e3e2 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SortGoalCommand.java @@ -0,0 +1,52 @@ +package seedu.address.logic.commands; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_SORT_FIELD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SORT_ORDER; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_GOALS; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.goal.exceptions.EmptyGoalListException; + +//@@author deborahlow97 +/** + * Sorts goal list in CollegeZone based on sort field entered by user. + */ +public class SortGoalCommand extends Command { + + public static final String COMMAND_WORD = "sortgoal"; + public static final String COMMAND_ALIAS = "sgoal"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Sorts CollegeZone's goals based on the field entered.\n" + + "Parameters: " + + PREFIX_SORT_FIELD + "FIELD (must be 'importance', 'startdatetime' or 'completion') " + + PREFIX_SORT_ORDER + "ORDER (must be either 'ascending' or 'descending')\n" + + "Example: " + COMMAND_WORD + " f/completion o/ascending"; + + public static final String MESSAGE_SUCCESS = "Sorted all goals by %s and %s"; + private String sortField; + private String sortOrder; + + public SortGoalCommand(String field, String order) { + this.sortField = field; + this.sortOrder = order; + } + + @Override + public CommandResult execute() throws CommandException { + try { + model.sortGoal(sortField, sortOrder); + } catch (EmptyGoalListException egle) { + throw new CommandException(Messages.MESSAGE_INVALID_SORT_COMMAND_USAGE); + } + model.updateFilteredGoalList(PREDICATE_SHOW_ALL_GOALS); + return new CommandResult(String.format(MESSAGE_SUCCESS, sortField, sortOrder)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortGoalCommand // instanceof handles nulls + && sortField.equals(((SortGoalCommand) other).sortField)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ThemeCommand.java b/src/main/java/seedu/address/logic/commands/ThemeCommand.java new file mode 100644 index 000000000000..6284eca60ac7 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ThemeCommand.java @@ -0,0 +1,47 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.events.ui.ThemeSwitchRequestEvent; + +//@@author deborahlow97 +/** + * Changes the CollegeZone colour theme to either dark, bubblegum or light. + */ +public class ThemeCommand extends Command { + public static final String COMMAND_WORD = "theme"; + public static final String COMMAND_ALIAS = "th"; + public static final String MESSAGE_SUCCESS = "Theme successfully changed!"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Changes the theme to the theme word entered.\n" + + "Parameters: COLOUR THEME\n" + + "(Colour theme words: dark, bubblegum, light)\n" + + "Example: " + COMMAND_WORD + " dark\n"; + public static final String MESSAGE_INVALID_THEME_COLOUR = "Theme colour entered is invalid.\n" + + "Possible theme colours:\n" + + "(Colour theme words: dark, bubblegum, light)\n"; + private final String themeColour; + + /** + * Creates a ThemeCommand based on the specified themeColour. + */ + public ThemeCommand (String themeColour) { + requireNonNull(themeColour); + this.themeColour = themeColour; + } + + @Override + public CommandResult execute() { + + EventsCenter.getInstance().post(new ThemeSwitchRequestEvent(themeColour)); + return new CommandResult(String.format(MESSAGE_SUCCESS, themeColour)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ThemeCommand // instanceof handles nulls + && themeColour.equals(((ThemeCommand) other).themeColour)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java index 1f3dcea8bbaa..7d62dcc53d38 100644 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java @@ -13,6 +13,7 @@ public class UndoCommand extends Command { public static final String COMMAND_WORD = "undo"; + public static final String COMMAND_ALIAS = "u"; public static final String MESSAGE_SUCCESS = "Undo success!"; public static final String MESSAGE_FAILURE = "No more commands to undo!"; diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3c729b388554..a7f8a04e107e 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -1,30 +1,43 @@ package seedu.address.logic.parser; 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_BIRTHDAY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CCA; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LEVEL_OF_FRIENDSHIP; 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.PREFIX_UNIT_NUMBER; import java.util.Set; + import java.util.stream.Stream; import seedu.address.commons.exceptions.IllegalValueException; + import seedu.address.logic.commands.AddCommand; + import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; + +import seedu.address.model.person.Birthday; +import seedu.address.model.person.Cca; +import seedu.address.model.person.LevelOfFriendship; +import seedu.address.model.person.Meet; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.UnitNumber; + import seedu.address.model.tag.Tag; + /** * Parses input arguments and creates a new AddCommand object */ public class AddCommandParser implements Parser { + //@@author deborahlow97 /** * Parses the given {@code String} of arguments in the context of the AddCommand * and returns an AddCommand object for execution. @@ -32,9 +45,11 @@ public class AddCommandParser implements Parser { */ public AddCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_BIRTHDAY, + PREFIX_LEVEL_OF_FRIENDSHIP, PREFIX_UNIT_NUMBER, PREFIX_CCA, PREFIX_TAG); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_BIRTHDAY, PREFIX_PHONE, PREFIX_UNIT_NUMBER, + PREFIX_LEVEL_OF_FRIENDSHIP, PREFIX_UNIT_NUMBER) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } @@ -42,18 +57,22 @@ public AddCommand parse(String args) throws ParseException { try { Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME)).get(); Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE)).get(); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL)).get(); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS)).get(); + Birthday birthday = ParserUtil.parseBirthday(argMultimap.getValue(PREFIX_BIRTHDAY)).get(); + UnitNumber unitNumber = ParserUtil.parseUnitNumber(argMultimap.getValue(PREFIX_UNIT_NUMBER)).get(); + LevelOfFriendship levelOfFriendship = ParserUtil.parseLevelOfFriendship(argMultimap + .getValue(PREFIX_LEVEL_OF_FRIENDSHIP)).get(); + Set ccaList = ParserUtil.parseCcas(argMultimap.getAllValues(PREFIX_CCA)); + Meet meetDate = new Meet(""); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - + Person person = new Person(name, phone, birthday, levelOfFriendship, unitNumber, ccaList, meetDate, + tagList); return new AddCommand(person); } catch (IllegalValueException ive) { throw new ParseException(ive.getMessage(), ive); } } + //@@author /** * Returns true if none of the prefixes contains empty {@code Optional} values in the given * {@code ArgumentMultimap}. diff --git a/src/main/java/seedu/address/logic/parser/AddGoalCommandParser.java b/src/main/java/seedu/address/logic/parser/AddGoalCommandParser.java new file mode 100644 index 000000000000..5caea9df00b8 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddGoalCommandParser.java @@ -0,0 +1,63 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GOAL_TEXT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_IMPORTANCE; + +import java.time.LocalDateTime; +import java.util.stream.Stream; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.AddGoalCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.goal.Completion; +import seedu.address.model.goal.EndDateTime; +import seedu.address.model.goal.Goal; +import seedu.address.model.goal.GoalText; +import seedu.address.model.goal.Importance; +import seedu.address.model.goal.StartDateTime; + +//@@author deborahlow97 +/** + * Parses input arguments and creates a new AddGoalCommand object + */ +public class AddGoalCommandParser implements Parser { + + public static final String EMPTY_END_DATE_TIME = ""; + public static final boolean INITIAL_COMPLETION_STATUS = false; + /** + * Parses the given {@code String} of arguments in the context of the AddGoalCommand + * and returns an AddGoalCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddGoalCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_IMPORTANCE, PREFIX_GOAL_TEXT); + + if (!arePrefixesPresent(argMultimap, PREFIX_IMPORTANCE, PREFIX_GOAL_TEXT) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddGoalCommand.MESSAGE_USAGE)); + } + + try { + Importance importance = ParserUtil.parseImportance(argMultimap.getValue(PREFIX_IMPORTANCE)).get(); + GoalText goalText = ParserUtil.parseGoalText(argMultimap.getValue(PREFIX_GOAL_TEXT)).get(); + StartDateTime startDateTime = new StartDateTime(LocalDateTime.now()); + EndDateTime endDateTime = new EndDateTime(EMPTY_END_DATE_TIME); + Completion completion = new Completion(INITIAL_COMPLETION_STATUS); + Goal goal = new Goal(importance, goalText, startDateTime, endDateTime, completion); + return new AddGoalCommand(goal); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/AddReminderCommandParser.java b/src/main/java/seedu/address/logic/parser/AddReminderCommandParser.java new file mode 100644 index 000000000000..460901d2e02f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddReminderCommandParser.java @@ -0,0 +1,69 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DATE_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_END_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMINDER_TEXT; +import static seedu.address.logic.parser.DateTimeParser.nattyDateAndTimeParser; + +import java.time.LocalDateTime; +import java.util.stream.Stream; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.AddReminderCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.reminder.DateTime; +import seedu.address.model.reminder.EndDateTime; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.reminder.ReminderText; + +//@@author fuadsahmawi +/** + * Parses input arguments and creates a new AddReminderCommand object + */ +public class AddReminderCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddReminderCommand + * and returns an AddReminderCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddReminderCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_REMINDER_TEXT, PREFIX_DATE, PREFIX_END_DATE); + + if (!arePrefixesPresent(argMultimap, PREFIX_REMINDER_TEXT, PREFIX_DATE, PREFIX_END_DATE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddReminderCommand.MESSAGE_USAGE)); + } + + if (nattyDateAndTimeParser(argMultimap.getValue(PREFIX_DATE).get()).get().compareTo( + nattyDateAndTimeParser(argMultimap.getValue(PREFIX_END_DATE).get()).get()) > 0 + || nattyDateAndTimeParser(argMultimap.getValue(PREFIX_END_DATE).get()).get().compareTo( + LocalDateTime.now()) < 0 + || nattyDateAndTimeParser(argMultimap.getValue(PREFIX_DATE).get()).get().compareTo( + LocalDateTime.now()) < 0) { + throw new ParseException(String.format(MESSAGE_INVALID_DATE_FORMAT, AddReminderCommand.MESSAGE_USAGE)); + } + + try { + ReminderText reminderText = ParserUtil.parseReminderText(argMultimap.getValue(PREFIX_REMINDER_TEXT)).get(); + DateTime dateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATE)).get(); + EndDateTime endDateTime = ParserUtil.parseEndDateTime(argMultimap.getValue(PREFIX_END_DATE)).get(); + Reminder reminder = new Reminder(reminderText, dateTime, endDateTime); + return new AddReminderCommand(reminder); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index b7d57f5db86a..43d29e155616 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -7,23 +7,42 @@ import java.util.regex.Pattern; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddGoalCommand; +import seedu.address.logic.commands.AddReminderCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CompleteGoalCommand; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteGoalCommand; +import seedu.address.logic.commands.DeleteMeetCommand; +import seedu.address.logic.commands.DeleteReminderCommand; import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.EditGoalCommand; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.HistoryCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.MeetCommand; +import seedu.address.logic.commands.OngoingGoalCommand; +import seedu.address.logic.commands.RateCommand; import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.SeekRaCommand; import seedu.address.logic.commands.SelectCommand; + +import seedu.address.logic.commands.ShowLofCommand; +import seedu.address.logic.commands.SortCommand; + +import seedu.address.logic.commands.SortGoalCommand; +import seedu.address.logic.commands.ThemeCommand; + import seedu.address.logic.commands.UndoCommand; import seedu.address.logic.parser.exceptions.ParseException; /** * Parses user input. */ +@SuppressWarnings("CheckStyle") public class AddressBookParser { /** @@ -51,27 +70,69 @@ public Command parseCommand(String userInput) throws ParseException { case AddCommand.COMMAND_WORD: return new AddCommandParser().parse(arguments); + case AddCommand.COMMAND_ALIAS: + return new AddCommandParser().parse(arguments); + case EditCommand.COMMAND_WORD: return new EditCommandParser().parse(arguments); + case EditCommand.COMMAND_ALIAS: + return new EditCommandParser().parse(arguments); + case SelectCommand.COMMAND_WORD: return new SelectCommandParser().parse(arguments); + case SelectCommand.COMMAND_ALIAS: + return new SelectCommandParser().parse(arguments); + case DeleteCommand.COMMAND_WORD: return new DeleteCommandParser().parse(arguments); + case DeleteCommand.COMMAND_ALIAS: + return new DeleteCommandParser().parse(arguments); + case ClearCommand.COMMAND_WORD: return new ClearCommand(); + case ClearCommand.COMMAND_ALIAS: + return new ClearCommand(); + case FindCommand.COMMAND_WORD: return new FindCommandParser().parse(arguments); + case FindCommand.COMMAND_ALIAS: + return new FindCommandParser().parse(arguments); + + case RateCommand.COMMAND_WORD: + return new RateCommandParser().parse(arguments); + + case RateCommand.COMMAND_ALIAS: + return new RateCommandParser().parse(arguments); + + case SeekRaCommand.COMMAND_WORD: + return new SeekRaCommandParser().parse(arguments); + + case SeekRaCommand.COMMAND_ALIAS: + return new SeekRaCommandParser().parse(arguments); + + case ShowLofCommand.COMMAND_WORD: + return new ShowLofCommandParser().parse(arguments); + + case ShowLofCommand.COMMAND_ALIAS: + return new ShowLofCommandParser().parse(arguments); + case ListCommand.COMMAND_WORD: return new ListCommand(); + case ListCommand.COMMAND_ALIAS: + return new ListCommand(); + case HistoryCommand.COMMAND_WORD: return new HistoryCommand(); + case HistoryCommand.COMMAND_ALIAS: + return new HistoryCommand(); + case ExitCommand.COMMAND_WORD: return new ExitCommand(); @@ -81,12 +142,77 @@ public Command parseCommand(String userInput) throws ParseException { case UndoCommand.COMMAND_WORD: return new UndoCommand(); + case UndoCommand.COMMAND_ALIAS: + return new UndoCommand(); + case RedoCommand.COMMAND_WORD: return new RedoCommand(); + case RedoCommand.COMMAND_ALIAS: + return new RedoCommand(); + + case MeetCommand.COMMAND_WORD: + case MeetCommand.COMMAND_ALIAS: + return new MeetCommandParser().parse(arguments); + + case DeleteMeetCommand.COMMAND_WORD: + return new DeleteMeetCommandParser().parse(arguments); + + case DeleteMeetCommand.COMMAND_ALIAS: + return new DeleteMeetCommandParser().parse(arguments); + + + case AddGoalCommand.COMMAND_WORD: + case AddGoalCommand.COMMAND_ALIAS_1: + case AddGoalCommand.COMMAND_ALIAS_2: + return new AddGoalCommandParser().parse(arguments); + + case EditGoalCommand.COMMAND_WORD: + case EditGoalCommand.COMMAND_ALIAS_1: + case EditGoalCommand.COMMAND_ALIAS_2: + return new EditGoalCommandParser().parse(arguments); + + case DeleteGoalCommand.COMMAND_WORD: + case DeleteGoalCommand.COMMAND_ALIAS_1: + case DeleteGoalCommand.COMMAND_ALIAS_2: + return new DeleteGoalCommandParser().parse(arguments); + + case CompleteGoalCommand.COMMAND_WORD: + case CompleteGoalCommand.COMMAND_ALIAS_1: + case CompleteGoalCommand.COMMAND_ALIAS_2: + return new CompleteGoalCommandParser().parse(arguments); + + case OngoingGoalCommand.COMMAND_WORD: + case OngoingGoalCommand.COMMAND_ALIAS_1: + case OngoingGoalCommand.COMMAND_ALIAS_2: + return new OngoingGoalCommandParser().parse(arguments); + + case AddReminderCommand.COMMAND_WORD: + case AddReminderCommand.COMMAND_ALIAS: + case AddReminderCommand.COMMAND_ALIAS_2: + return new AddReminderCommandParser().parse(arguments); + + case DeleteReminderCommand.COMMAND_WORD: + case DeleteReminderCommand.COMMAND_ALIAS: + case DeleteReminderCommand.COMMAND_ALIAS_2: + return new DeleteReminderCommandParser().parse(arguments); + + case SortCommand.COMMAND_WORD: + return new SortCommandParser().parse(arguments); + + + case ThemeCommand.COMMAND_WORD: + case ThemeCommand.COMMAND_ALIAS: + return new ThemeCommandParser().parse(arguments); + + case SortGoalCommand.COMMAND_WORD: + case SortGoalCommand.COMMAND_ALIAS: + return new SortGoalCommandParser().parse(arguments); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } + } } diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf1190..088e185292cb 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -8,8 +8,19 @@ public class CliSyntax { /* Prefix definitions */ public static final Prefix PREFIX_NAME = new Prefix("n/"); 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_BIRTHDAY = new Prefix("b/"); + public static final Prefix PREFIX_LEVEL_OF_FRIENDSHIP = new Prefix("*/"); + public static final Prefix PREFIX_UNIT_NUMBER = new Prefix("u/"); + public static final Prefix PREFIX_CCA = new Prefix("cca/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_DATE = new Prefix("d/"); + public static final Prefix PREFIX_GOAL_TEXT = new Prefix("text/"); + public static final Prefix PREFIX_IMPORTANCE = new Prefix("impt/"); + public static final Prefix PREFIX_REMINDER_TEXT = new Prefix("text/"); + public static final Prefix PREFIX_END_DATE = new Prefix("e/"); + public static final Prefix PREFIX_SORT_FIELD = new Prefix("f/"); + public static final Prefix PREFIX_SORT_ORDER = new Prefix("o/"); + + } diff --git a/src/main/java/seedu/address/logic/parser/CompleteGoalCommandParser.java b/src/main/java/seedu/address/logic/parser/CompleteGoalCommandParser.java new file mode 100644 index 000000000000..785998a46bcd --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CompleteGoalCommandParser.java @@ -0,0 +1,49 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.DateTimeParser.properDateTimeFormat; + +import java.time.LocalDateTime; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.CompleteGoalCommand; +import seedu.address.logic.commands.CompleteGoalCommand.CompleteGoalDescriptor; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.goal.Completion; +import seedu.address.model.goal.EndDateTime; + +//@@author deborahlow97 +/** + * Parses input arguments and creates a new CompleteGoalCommand object + */ +public class CompleteGoalCommandParser implements Parser { + + public static final boolean COMPLETED_BOOLEAN_VALUE = true; + + /** + * Parses the given {@code String} of arguments in the context of the CompleteGoalCommand + * and returns an CompleteGoalCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CompleteGoalCommand parse(String args) throws ParseException { + + Index index; + try { + index = ParserUtil.parseIndex(args); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, CompleteGoalCommand.MESSAGE_USAGE)); + } + + CompleteGoalDescriptor completeGoalDescriptor = new CompleteGoalDescriptor(); + + Completion completion = new Completion(COMPLETED_BOOLEAN_VALUE); + EndDateTime endDateTime = new EndDateTime(properDateTimeFormat(LocalDateTime.now())); + completeGoalDescriptor.setCompletion(completion); + completeGoalDescriptor.setEndDateTime(endDateTime); + + + return new CompleteGoalCommand(index, completeGoalDescriptor); + } +} diff --git a/src/main/java/seedu/address/logic/parser/DateTimeParser.java b/src/main/java/seedu/address/logic/parser/DateTimeParser.java new file mode 100644 index 000000000000..7494b5bf6628 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DateTimeParser.java @@ -0,0 +1,118 @@ +package seedu.address.logic.parser; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.TextStyle; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Optional; + +import com.joestelmach.natty.DateGroup; +import com.joestelmach.natty.Parser; + +//@@author deborahlow97 +/** + * Contains utility methods used for parsing DateTime in the various *Parser classes. + */ +public class DateTimeParser { + + private static final int BEGIN_INDEX = 6; + /** + * Parses user input String specified{@code args} into LocalDateTime objects + * + * @return Empty Optional if args could not be parsed + * @Disclaimer : The parser used is a NLP API called 'natty' developed by 'Joe Stelmach' + */ + public static Optional nattyDateAndTimeParser(String args) { + if (args == null || args.isEmpty()) { + return Optional.empty(); + } + + Parser parser = new Parser(); + List groups = parser.parse(args); + + //Cannot be parsed + if (groups.size() <= 0) { + return Optional.empty(); + } + + DateGroup dateGroup = (DateGroup) groups.get(0); + if (dateGroup.getDates().size() < 0) { + return Optional.empty(); + } + + Date date = dateGroup.getDates().get(0); + + LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); + return Optional.ofNullable(localDateTime); + } + + /** + * Receives a LocalDateTime and formats the {@code dateTime} + * + * @return a formatted dateTime in String + */ + public static String properDateTimeFormat(LocalDateTime dateTime) { + StringBuilder builder = new StringBuilder(); + int day = dateTime.getDayOfMonth(); + String month = dateTime.getMonth().getDisplayName(TextStyle.FULL, Locale.ENGLISH); + int year = dateTime.getYear(); + int hour = dateTime.getHour(); + int minute = dateTime.getMinute(); + builder.append("Date: ") + .append(day) + .append(" ") + .append(month) + .append(" ") + .append(year) + .append(", Time: ") + .append(String.format("%02d", hour)) + .append(":") + .append(String.format("%02d", minute)); + return builder.toString(); + } + + public static LocalDateTime getLocalDateTimeFromProperDateTime(String properDateTimeString) { + String trimmedArgs = properDateTimeString.trim(); + int size = trimmedArgs.length(); + String stringFormat = properDateTimeString.substring(BEGIN_INDEX, size); + stringFormat = stringFormat.replace(", Time: ", ""); + return nattyDateAndTimeParser(stringFormat).get(); + } + + /** + * Receives a LocalDateTime and formats the {@code dateTime} + * + * @return a formatted dateTime in String + */ + public static String properReminderDateTimeFormat(LocalDateTime dateTime) { + StringBuilder builder = new StringBuilder(); + int day = dateTime.getDayOfMonth(); + String month = dateTime.getMonth().getDisplayName(TextStyle.FULL, Locale.ENGLISH); + int year = dateTime.getYear(); + int hour = dateTime.getHour(); + int minute = dateTime.getMinute(); + builder.append(day) + .append("/") + .append(month) + .append("/") + .append(year) + .append(" ") + .append(String.format("%02d", hour)) + .append(":") + .append(String.format("%02d", minute)); + return builder.toString(); + } + + public static boolean containsDateAndTime(String args) { + return nattyDateAndTimeParser(args).isPresent(); + } + + public static LocalDateTime getLocalDateTimeFromString(String dateString) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + LocalDateTime dateTime = LocalDateTime.parse(dateString, formatter); + return dateTime; + } +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteGoalCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteGoalCommandParser.java new file mode 100644 index 000000000000..97ea93935f3c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteGoalCommandParser.java @@ -0,0 +1,31 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.DeleteGoalCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author deborahlow97 +/** + * Parses input arguments and creates a new DeleteGoalCommand object + */ +public class DeleteGoalCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteGoalCommand + * and returns an DeleteGoalCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteGoalCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteGoalCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteGoalCommand.MESSAGE_USAGE)); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteMeetCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteMeetCommandParser.java new file mode 100644 index 000000000000..ccd208ca70e3 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteMeetCommandParser.java @@ -0,0 +1,30 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.DeleteMeetCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteMeetCommand object + */ +public class DeleteMeetCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteMeetCommand + * and returns an DeleteMeetCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteMeetCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new DeleteMeetCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteMeetCommand.MESSAGE_USAGE)); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteReminderCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteReminderCommandParser.java new file mode 100644 index 000000000000..16a2aaa1c2bf --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteReminderCommandParser.java @@ -0,0 +1,61 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMINDER_TEXT; +import static seedu.address.logic.parser.DateTimeParser.nattyDateAndTimeParser; +import static seedu.address.logic.parser.DateTimeParser.properReminderDateTimeFormat; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.stream.Stream; + +import seedu.address.logic.commands.DeleteReminderCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.reminder.ReminderTextPredicate; + +//@@author fuadsahmawi +/** + * Parses input arguments and creates a new DeleteReminderCommand object + */ +public class DeleteReminderCommandParser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteReminderCommand + * and returns an DeleteReminderCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteReminderCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_REMINDER_TEXT, PREFIX_DATE); + + if (!arePrefixesPresent(argMultimap, PREFIX_REMINDER_TEXT, PREFIX_DATE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteReminderCommand.MESSAGE_USAGE)); + } + + String reminderText = argMultimap.getValue(PREFIX_REMINDER_TEXT).get(); + String dateTime = argMultimap.getValue(PREFIX_DATE).get(); + LocalDateTime localDateTime = nattyDateAndTimeParser(dateTime).get(); + dateTime = properReminderDateTimeFormat(localDateTime); + String trimmedArgs = reminderText.trim(); + + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteReminderCommand.MESSAGE_USAGE)); + } + + String[] nameKeywords = trimmedArgs.split("\\s+"); + + return new DeleteReminderCommand(new ReminderTextPredicate(Arrays.asList(nameKeywords)), dateTime); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index c9cdbed26cf1..2cd79c35f0ca 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -2,11 +2,13 @@ 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_BIRTHDAY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CCA; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LEVEL_OF_FRIENDSHIP; 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.PREFIX_UNIT_NUMBER; import java.util.Collection; import java.util.Collections; @@ -18,6 +20,7 @@ import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Cca; import seedu.address.model.tag.Tag; /** @@ -33,7 +36,8 @@ public class EditCommandParser implements Parser { public EditCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_BIRTHDAY, + PREFIX_LEVEL_OF_FRIENDSHIP, PREFIX_UNIT_NUMBER, PREFIX_CCA, PREFIX_TAG); Index index; @@ -47,8 +51,13 @@ public EditCommand parse(String args) throws ParseException { try { ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME)).ifPresent(editPersonDescriptor::setName); ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE)).ifPresent(editPersonDescriptor::setPhone); - ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL)).ifPresent(editPersonDescriptor::setEmail); - ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS)).ifPresent(editPersonDescriptor::setAddress); + ParserUtil.parseBirthday(argMultimap.getValue(PREFIX_BIRTHDAY)) + .ifPresent(editPersonDescriptor::setBirthday); + ParserUtil.parseLevelOfFriendship(argMultimap.getValue(PREFIX_LEVEL_OF_FRIENDSHIP)) + .ifPresent(editPersonDescriptor::setLevelOfFriendship); + ParserUtil.parseUnitNumber(argMultimap.getValue(PREFIX_UNIT_NUMBER)) + .ifPresent(editPersonDescriptor::setUnitNumber); + parseCcasForEdit(argMultimap.getAllValues(PREFIX_CCA)).ifPresent(editPersonDescriptor::setCcas); parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); } catch (IllegalValueException ive) { throw new ParseException(ive.getMessage(), ive); @@ -61,6 +70,23 @@ public EditCommand parse(String args) throws ParseException { return new EditCommand(index, editPersonDescriptor); } + //@@author deborahlow97 + /** + * Parses {@code Collection ccas} into a {@code Set} if {@code ccas} is non-empty. + * If {@code ccas} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero ccas. + */ + private Optional> parseCcasForEdit(Collection ccas) throws IllegalValueException { + assert ccas != null; + + if (ccas.isEmpty()) { + return Optional.empty(); + } + Collection ccaSet = ccas.size() == 1 && ccas.contains("") ? Collections.emptySet() : ccas; + return Optional.of(ParserUtil.parseCcas(ccaSet)); + } + + //@@author /** * 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 diff --git a/src/main/java/seedu/address/logic/parser/EditGoalCommandParser.java b/src/main/java/seedu/address/logic/parser/EditGoalCommandParser.java new file mode 100644 index 000000000000..b89b6494488c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EditGoalCommandParser.java @@ -0,0 +1,54 @@ +package seedu.address.logic.parser; + +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_GOAL_TEXT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_IMPORTANCE; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; + +import seedu.address.logic.commands.EditGoalCommand.EditGoalDescriptor; +import seedu.address.logic.commands.EditGoalCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author deborahlow97 +/** + * Parses input arguments and creates a new EditGoalCommand object + */ +public class EditGoalCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditGoalCommand + * and returns an EditGoalCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public EditGoalCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_GOAL_TEXT, PREFIX_IMPORTANCE); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditGoalCommand.MESSAGE_USAGE)); + } + + EditGoalDescriptor editGoalDescriptor = new EditGoalDescriptor(); + try { + ParserUtil.parseGoalText(argMultimap.getValue(PREFIX_GOAL_TEXT)).ifPresent(editGoalDescriptor::setGoalText); + ParserUtil.parseImportance(argMultimap.getValue(PREFIX_IMPORTANCE)) + .ifPresent(editGoalDescriptor::setImportance); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + + if (!editGoalDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditGoalCommand.MESSAGE_NOT_EDITED); + } + + return new EditGoalCommand(index, editGoalDescriptor); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index b186a967cb94..7f3499f9b5ae 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -1,13 +1,18 @@ package seedu.address.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Arrays; import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindCommand.FindPersonDescriptor; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.TagContainsKeywordsPredicate; +//@@author fuadsahmawi /** * Parses input arguments and creates a new FindCommand object */ @@ -19,15 +24,26 @@ public class FindCommandParser implements Parser { * @throws ParseException if the user input does not conform the expected format */ public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_TAG); - String[] nameKeywords = trimmedArgs.split("\\s+"); + FindPersonDescriptor findPersonDescriptor = new FindPersonDescriptor(); - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } + argMultimap.getValue(PREFIX_NAME).ifPresent(findPersonDescriptor::setNameKeywords); + argMultimap.getValue(PREFIX_TAG).ifPresent(findPersonDescriptor::setTagKeywords); + + if (!findPersonDescriptor.isAnyFieldEdited()) { + throw new ParseException(FindCommand.MESSAGE_NOT_EDITED); + } + + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + return new FindCommand( + new NameContainsKeywordsPredicate(Arrays.asList(findPersonDescriptor.getNameKeywords()))); + } else { + return new FindCommand( + new TagContainsKeywordsPredicate(Arrays.asList(findPersonDescriptor.getTagKeyWords()))); + } + } } diff --git a/src/main/java/seedu/address/logic/parser/MeetCommandParser.java b/src/main/java/seedu/address/logic/parser/MeetCommandParser.java new file mode 100644 index 000000000000..97fb9cf0b532 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/MeetCommandParser.java @@ -0,0 +1,55 @@ +package seedu.address.logic.parser; + +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_DATE; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; + +import seedu.address.commons.exceptions.IllegalValueException; + +import seedu.address.logic.commands.MeetCommand; + +import seedu.address.logic.parser.exceptions.ParseException; + +import seedu.address.model.person.Meet; + + + + + +//@@author sham-sheer +/** + * Parses input arguments and creates a new {@code RemarkCommand} object + */ +public class MeetCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the {@code MeetCommand} + * and returns a {@code MeetCommand} object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public MeetCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_DATE); + + Index index; + + if (!arePrefixesPresent(argMultimap, PREFIX_DATE) || argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, MeetCommand.MESSAGE_USAGE)); + } + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + Meet meetDate = ParserUtil.parseMeetDate(argMultimap.getValue(PREFIX_DATE)).get(); + return new MeetCommand(index, meetDate); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/OngoingGoalCommandParser.java b/src/main/java/seedu/address/logic/parser/OngoingGoalCommandParser.java new file mode 100644 index 000000000000..f72e0f8b4d6c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/OngoingGoalCommandParser.java @@ -0,0 +1,48 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Optional; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.OngoingGoalCommand; +import seedu.address.logic.commands.OngoingGoalCommand.OngoingGoalDescriptor; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.goal.Completion; +import seedu.address.model.goal.EndDateTime; + +//@@author deborahlow97 +/** + * Parses input arguments and creates a new OngoingGoalCommand object + */ +public class OngoingGoalCommandParser implements Parser { + + public static final boolean ONGOING_BOOLEAN_VALUE = false; + /** + * Parses the given {@code String} of arguments in the context of the OngoingGoalCommand + * and returns an OngoingGoalCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public OngoingGoalCommand parse(String args) throws ParseException { + + Index index; + try { + index = ParserUtil.parseIndex(args); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, OngoingGoalCommand.MESSAGE_USAGE)); + } + + OngoingGoalDescriptor ongoingGoalDescriptor = new OngoingGoalDescriptor(); + + Optional empty = Optional.empty(); + Completion completion = new Completion(ONGOING_BOOLEAN_VALUE); + EndDateTime endDateTime = new EndDateTime(""); + ongoingGoalDescriptor.setCompletion(completion); + ongoingGoalDescriptor.setEndDateTime(endDateTime); + + + return new OngoingGoalCommand(index, ongoingGoalDescriptor); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 5d6d4ae3f7b1..10379d4e2369 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -10,10 +10,18 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.StringUtil; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; +import seedu.address.model.goal.GoalText; +import seedu.address.model.goal.Importance; +import seedu.address.model.person.Birthday; +import seedu.address.model.person.Cca; +import seedu.address.model.person.LevelOfFriendship; +import seedu.address.model.person.Meet; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; +import seedu.address.model.person.UnitNumber; +import seedu.address.model.reminder.DateTime; +import seedu.address.model.reminder.EndDateTime; +import seedu.address.model.reminder.ReminderText; import seedu.address.model.tag.Tag; /** @@ -29,6 +37,8 @@ public class ParserUtil { public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; public static final String MESSAGE_INSUFFICIENT_PARTS = "Number of parts must be more than 1."; + public static final String MESSAGE_INVALID_SORT_FIELD = "Sort field entered is not a valid field."; + public static final String MESSAGE_INVALID_ORDER_FIELD = "Sort order field entered is not a valid field."; /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be @@ -91,54 +101,135 @@ public static Optional parsePhone(Optional phone) throws IllegalV return phone.isPresent() ? Optional.of(parsePhone(phone.get())) : Optional.empty(); } + //@@author deborahlow97 /** - * Parses a {@code String address} into an {@code Address}. + * Parses a {@code String birthday} into an {@code birthday}. * Leading and trailing whitespaces will be trimmed. * * @throws IllegalValueException if the given {@code address} is invalid. */ - public static Address parseAddress(String address) throws IllegalValueException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new IllegalValueException(Address.MESSAGE_ADDRESS_CONSTRAINTS); + public static Birthday parseBirthday(String birthday) throws IllegalValueException { + requireNonNull(birthday); + String trimmedBirthday = birthday.trim(); + if (!Birthday.isValidBirthday(trimmedBirthday)) { + throw new IllegalValueException(Birthday.MESSAGE_BIRTHDAY_CONSTRAINTS); } - return new Address(trimmedAddress); + return new Birthday(trimmedBirthday); } /** - * Parses a {@code Optional address} into an {@code Optional
} if {@code address} is present. + * Parses a {@code Optional birthday} into an {@code Optional} if {@code birthday} is present. * See header comment of this class regarding the use of {@code Optional} parameters. */ - public static Optional
parseAddress(Optional address) throws IllegalValueException { - requireNonNull(address); - return address.isPresent() ? Optional.of(parseAddress(address.get())) : Optional.empty(); + public static Optional parseBirthday(Optional birthday) throws IllegalValueException { + requireNonNull(birthday); + return birthday.isPresent() ? Optional.of(parseBirthday(birthday.get())) : Optional.empty(); } /** - * Parses a {@code String email} into an {@code Email}. + * Parses a {@code String levelOfFriendship} into a {@code LevelOfFriendship}. * Leading and trailing whitespaces will be trimmed. * - * @throws IllegalValueException if the given {@code email} is invalid. + * @throws IllegalValueException if the given {@code levelOfFriendship} is invalid. */ - public static Email parseEmail(String email) throws IllegalValueException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new IllegalValueException(Email.MESSAGE_EMAIL_CONSTRAINTS); + public static LevelOfFriendship parseLevelOfFriendship(String levelOfFriendship) throws IllegalValueException { + requireNonNull(levelOfFriendship); + String trimmedLevelOfFriendship = levelOfFriendship.trim(); + if (!LevelOfFriendship.isValidLevelOfFriendship(trimmedLevelOfFriendship)) { + throw new IllegalValueException(LevelOfFriendship.MESSAGE_LEVEL_OF_FRIENDSHIP_CONSTRAINTS); } - return new Email(trimmedEmail); + return new LevelOfFriendship(trimmedLevelOfFriendship); } /** - * Parses a {@code Optional email} into an {@code Optional} if {@code email} is present. + * Parses a {@code Optional name} into an {@code Optional} if {@code name} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseLevelOfFriendship(Optional levelOfFriendship) + throws IllegalValueException { + requireNonNull(levelOfFriendship); + return levelOfFriendship.isPresent() ? Optional.of(parseLevelOfFriendship(levelOfFriendship.get())) + : Optional.empty(); + } + + /** + * Parses a {@code String unitNumber} into an {@code UnitNumber}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code unitNumber} is invalid. + */ + public static UnitNumber parseUnitNumber(String unitNumber) throws IllegalValueException { + requireNonNull(unitNumber); + String trimmedUnitNumber = unitNumber.trim(); + if (!UnitNumber.isValidUnitNumber(trimmedUnitNumber)) { + throw new IllegalValueException(UnitNumber.MESSAGE_UNIT_NUMBER_CONSTRAINTS); + } + return new UnitNumber(trimmedUnitNumber); + } + + /** + * Parses a {@code Optional unitNumber} into an {@code Optional} if {@code unitNumber} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseUnitNumber(Optional unitNumber) throws IllegalValueException { + requireNonNull(unitNumber); + return unitNumber.isPresent() ? Optional.of(parseUnitNumber(unitNumber.get())) : Optional.empty(); + } + //@@author sham-sheer + /** + * Parses a {@code String unitNumber} into an {@code UnitNumber}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code unitNumber} is invalid. + */ + public static Meet parseMeetDate(String meetDate) throws IllegalValueException { + requireNonNull(meetDate); + String trimmedMeetDate = meetDate.trim(); + if (!Meet.isValidDate(meetDate)) { + throw new IllegalValueException(Meet.MESSAGE_DATE_CONSTRAINTS); + } + return new Meet(trimmedMeetDate); + } + /** + * Parses a {@code Optional meetDate} into an {@code Optional} if {@code meetDate} + * is present. * See header comment of this class regarding the use of {@code Optional} parameters. */ - public static Optional parseEmail(Optional email) throws IllegalValueException { - requireNonNull(email); - return email.isPresent() ? Optional.of(parseEmail(email.get())) : Optional.empty(); + public static Optional parseMeetDate(Optional meetDate) throws IllegalValueException { + requireNonNull(meetDate); + return meetDate.isPresent() ? Optional.of(parseMeetDate(meetDate.get())) : Optional.empty(); + } + + //@@author deborahlow97 + /** + * Parses a {@code String cca} into a {@code Cca} + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code cca} is invalid. + */ + public static Cca parseCca(String cca) throws IllegalValueException { + requireNonNull(cca); + String trimmedCca = cca.trim(); + if (!Cca.isValidCcaName(trimmedCca)) { + throw new IllegalValueException(Cca.MESSAGE_CCA_CONSTRAINTS); + } + return new Cca(trimmedCca); + } + + /** + * Parses {@code Collection ccas} into a {@code Set}. + */ + public static Set parseCcas(Collection ccas) throws IllegalValueException { + requireNonNull(ccas); + final Set ccaSet = new HashSet<>(); + for (String ccaName : ccas) { + ccaSet.add(parseCca(ccaName)); + } + return ccaSet; } + //@@author /** * Parses a {@code String tag} into a {@code Tag}. * Leading and trailing whitespaces will be trimmed. @@ -165,4 +256,188 @@ public static Set parseTags(Collection tags) throws IllegalValueExc } return tagSet; } + + //@@author deborahlow97 + /** + * Parses a {@code String importance} into an {@code Importance}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code importance} is invalid. + */ + public static Importance parseImportance(String importance) throws IllegalValueException { + requireNonNull(importance); + String trimmedImportance = importance.trim(); + if (!Importance.isValidImportance(trimmedImportance)) { + throw new IllegalValueException(Importance.MESSAGE_IMPORTANCE_CONSTRAINTS); + } + return new Importance(trimmedImportance); + } + + /** + * Parses a {@code Optional importance} into an {@code Optional} if {@code importance} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseImportance(Optional importance) throws IllegalValueException { + requireNonNull(importance); + return importance.isPresent() ? Optional.of(parseImportance(importance.get())) : Optional.empty(); + } + + /** + * Parses a {@code String goalText} into an {@code GoalText}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code goalText} is invalid. + */ + public static GoalText parseGoalText(String goalText) throws IllegalValueException { + requireNonNull(goalText); + String trimmedGoalText = goalText.trim(); + if (!GoalText.isValidGoalText(trimmedGoalText)) { + throw new IllegalValueException(GoalText.MESSAGE_GOAL_TEXT_CONSTRAINTS); + } + return new GoalText(trimmedGoalText); + } + + /** + * Parses a {@code Optional goalText} into an {@code Optional} if {@code goalText} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseGoalText(Optional goalText) throws IllegalValueException { + requireNonNull(goalText); + return goalText.isPresent() ? Optional.of(parseGoalText(goalText.get())) : Optional.empty(); + } + + //@@author fuadsahmawi + + /** + * Parses a {@code String reminderText} into an {@code ReminderText}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code reminderText} is invalid. + */ + public static ReminderText parseReminderText(String reminderText) throws IllegalValueException { + requireNonNull(reminderText); + String trimmedReminderText = reminderText.trim(); + if (!ReminderText.isValidReminderText(trimmedReminderText)) { + throw new IllegalValueException(ReminderText.MESSAGE_REMINDER_TEXT_CONSTRAINTS); + } + return new ReminderText(trimmedReminderText); + } + + /** + * Parses a {@code Optional reminderText} into an {@code Optional} if {@code reminderText} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseReminderText(Optional reminderText) throws IllegalValueException { + requireNonNull(reminderText); + return reminderText.isPresent() ? Optional.of(parseReminderText(reminderText.get())) : Optional.empty(); + } + + /** + * Parses a {@code String reminderText} into an {@code ReminderText}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code reminderText} is invalid. + */ + public static DateTime parseDateTime(String dateTime) throws IllegalValueException { + requireNonNull(dateTime); + String trimmedDateTime = dateTime.trim(); + if (!DateTime.isValidDateTime(trimmedDateTime)) { + throw new IllegalValueException(DateTime.MESSAGE_DATE_TIME_CONSTRAINTS); + } + return new DateTime(trimmedDateTime); + } + + /** + * Parses a {@code Optional reminderText} into an {@code Optional} if {@code reminderText} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseDateTime(Optional dateTime) throws IllegalValueException { + requireNonNull(dateTime); + return dateTime.isPresent() ? Optional.of(parseDateTime(dateTime.get())) : Optional.empty(); + } + + /** + * Parses a {@code String reminderText} into an {@code ReminderText}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code reminderText} is invalid. + */ + public static EndDateTime parseEndDateTime(String endDateTime) throws IllegalValueException { + requireNonNull(endDateTime); + String trimmedEndDateTime = endDateTime.trim(); + if (!DateTime.isValidDateTime(trimmedEndDateTime)) { + throw new IllegalValueException(EndDateTime.MESSAGE_END_DATE_TIME_CONSTRAINTS); + } + return new EndDateTime(trimmedEndDateTime); + } + + /** + * Parses a {@code Optional reminderText} into an {@code Optional} if {@code reminderText} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseEndDateTime(Optional endDateTime) throws IllegalValueException { + requireNonNull(endDateTime); + return endDateTime.isPresent() ? Optional.of(parseEndDateTime(endDateTime.get())) : Optional.empty(); + } + + //@@author deborahlow97 + /** + * Parses a {@code String sortField} and checks if it is a valid sortField parameter. + * + * @throws IllegalValueException if specified String is an invalid field. + */ + public static String parseSortGoalField(String sortField) throws IllegalValueException { + String trimmedSortField = sortField.trim(); + switch (trimmedSortField) { + case "importance": + case "completion": + case "startdatetime": + return trimmedSortField; + default: + throw new IllegalValueException(MESSAGE_INVALID_SORT_FIELD); + } + } + + /** + * Parses a {@code Optional sortField} into an {@code Optional} if {@code sortField} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseSortGoalField(Optional sortField) throws IllegalValueException { + requireNonNull(sortField); + return sortField.isPresent() ? Optional.of(parseSortGoalField(sortField.get())) : Optional.empty(); + } + + /** + * Parses a {@code String order} and check if it is a valid order parameter. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code order} is invalid. + */ + public static String parseSortGoalOrder(String order) throws IllegalValueException { + requireNonNull(order); + String trimmedOrder = order.trim(); + switch (trimmedOrder) { + case "ascending": + case "descending": + return trimmedOrder; + default: + throw new IllegalValueException(MESSAGE_INVALID_ORDER_FIELD); + } + } + + /** + * Parses a {@code Optional sortOrder} into an {@code Optional} if {@code sortOrder} + * is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseSortGoalOrder(Optional sortOrder) throws IllegalValueException { + requireNonNull(sortOrder); + return sortOrder.isPresent() ? Optional.of(parseSortGoalOrder(sortOrder.get())) : Optional.empty(); + } } diff --git a/src/main/java/seedu/address/logic/parser/RateCommandParser.java b/src/main/java/seedu/address/logic/parser/RateCommandParser.java new file mode 100644 index 000000000000..a2cee9ec80f6 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RateCommandParser.java @@ -0,0 +1,73 @@ +package seedu.address.logic.parser; + +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_LEVEL_OF_FRIENDSHIP; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.RateCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author zuweitrack +/** + * Parses input arguments and creates a new RateCommand object + */ +public class RateCommandParser { + + /** + * Returns true if the level of friendship prefix "/*" is present + */ + private static boolean isPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix prefix) { + return Stream.of(prefix).allMatch(groupPrefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if the level of friendship is between 1 - 10 + */ + private static boolean containsValidRange(String levelOfFriendship) { + return levelOfFriendship.matches("0?[1-9]|[1][0]"); + } + + /** + * Parses the given {@code String} of arguments in the context of the RateCommand + * and returns an RateCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RateCommand parse (String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argumentMultimap = ArgumentTokenizer.tokenize(args, PREFIX_LEVEL_OF_FRIENDSHIP); + + if (!isPrefixesPresent(argumentMultimap, PREFIX_LEVEL_OF_FRIENDSHIP)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RateCommand.MESSAGE_USAGE)); + } + String levelOfFriendship = argumentMultimap.getValue(PREFIX_LEVEL_OF_FRIENDSHIP).get(); + + if (!containsValidRange(levelOfFriendship)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RateCommand.MESSAGE_USAGE)); + } + + String preamble; + String[] indexString; + List indexList = new ArrayList<>(); + + try { + preamble = argumentMultimap.getPreamble(); + indexString = preamble.split("\\s+"); + for (String index : indexString) { + indexList.add(ParserUtil.parseIndex(index)); + } + + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RateCommand.MESSAGE_USAGE)); + } + + return new RateCommand(indexList, new String(levelOfFriendship)); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/SeekRaCommandParser.java b/src/main/java/seedu/address/logic/parser/SeekRaCommandParser.java new file mode 100644 index 000000000000..96c861b33641 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SeekRaCommandParser.java @@ -0,0 +1,37 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.SeekRaCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.UnitNumberContainsKeywordsPredicate; + +//@@author zuweitrack +/** + * Parses input arguments and creates a new SeekRaCommand object + */ +public class SeekRaCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the SeekRaCommand + * and returns an SeekRaCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SeekRaCommand parse(String args) throws ParseException { + + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SeekRaCommand.MESSAGE_USAGE)); + } + + trimmedArgs = trimmedArgs + " " + "RA"; + + String[] nameKeywords = (trimmedArgs.split("\\s+")); + + return new SeekRaCommand(new UnitNumberContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ShowLofCommandParser.java b/src/main/java/seedu/address/logic/parser/ShowLofCommandParser.java new file mode 100644 index 000000000000..342bfe22011f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ShowLofCommandParser.java @@ -0,0 +1,35 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.ShowLofCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.LofContainsValuePredicate; + +//@@author zuweitrack +/** + * Parses input arguments and creates a new ShowLofCommand object + */ +public class ShowLofCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ShowLofCommand + * and returns an ShowLofCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ShowLofCommand parse(String args) throws ParseException { + + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ShowLofCommand.MESSAGE_USAGE)); + } + + String[] nameKeywords = (trimmedArgs.split("\\s+")); + + return new ShowLofCommand(new LofContainsValuePredicate(Arrays.asList(nameKeywords))); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/SortCommandParser.java b/src/main/java/seedu/address/logic/parser/SortCommandParser.java new file mode 100644 index 000000000000..5a77a8d985a5 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SortCommandParser.java @@ -0,0 +1,31 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.SortCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author sham-sheer +/** + * Parses input arguments and creates a new DeleteCommand object + */ +public class SortCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteCommand + * and returns an DeleteCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SortCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new SortCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortCommand.MESSAGE_USAGE)); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/SortGoalCommandParser.java b/src/main/java/seedu/address/logic/parser/SortGoalCommandParser.java new file mode 100644 index 000000000000..e8bebc23696b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SortGoalCommandParser.java @@ -0,0 +1,43 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SORT_FIELD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SORT_ORDER; + +import java.util.stream.Stream; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.SortGoalCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author deborahlow97 +/** + * Parses input arguments and creates a new SortGoalCommand object + */ +public class SortGoalCommandParser implements Parser { + + @Override + public SortGoalCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_SORT_FIELD, PREFIX_SORT_ORDER); + if (!arePrefixesPresent(argMultimap, PREFIX_SORT_FIELD, PREFIX_SORT_ORDER) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortGoalCommand.MESSAGE_USAGE)); + } + try { + String sortField = ParserUtil.parseSortGoalField(argMultimap.getValue(PREFIX_SORT_FIELD)).get(); + String sortOrder = ParserUtil.parseSortGoalOrder(argMultimap.getValue(PREFIX_SORT_ORDER)).get(); + return new SortGoalCommand(sortField, sortOrder); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortGoalCommand.MESSAGE_USAGE)); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ThemeCommandParser.java b/src/main/java/seedu/address/logic/parser/ThemeCommandParser.java new file mode 100644 index 000000000000..16c5827c897b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ThemeCommandParser.java @@ -0,0 +1,51 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.model.ThemeColourUtil.getThemeHashMap; + +import java.util.HashMap; + +import seedu.address.logic.commands.ThemeCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author deborahlow97 +/** + * Parses input arguments and creates a new ThemeCommand object + */ +public class ThemeCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ThemeCommand + * and returns a ThemeCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ThemeCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ThemeCommand.MESSAGE_USAGE)); + } + if (!isValidThemeColour(trimmedArgs)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ThemeCommand.MESSAGE_INVALID_THEME_COLOUR)); + + } + return new ThemeCommand(trimmedArgs); + } + + /** + * + * @param themeColour + * @return + */ + private boolean isValidThemeColour(String themeColour) { + HashMap themes = getThemeHashMap(); + if (themes.containsKey(themeColour.toLowerCase())) { + return true; + } else { + return false; + } + + } +} + diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index f8d0260de159..13f3a6786e46 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -11,10 +11,23 @@ import java.util.stream.Collectors; import javafx.collections.ObservableList; +import seedu.address.commons.core.index.Index; +import seedu.address.model.goal.Goal; +import seedu.address.model.goal.UniqueGoalList; +import seedu.address.model.goal.exceptions.DuplicateGoalException; +import seedu.address.model.goal.exceptions.EmptyGoalListException; +import seedu.address.model.goal.exceptions.GoalNotFoundException; +import seedu.address.model.person.Cca; +import seedu.address.model.person.Meet; import seedu.address.model.person.Person; +import seedu.address.model.person.UniqueCcaList; import seedu.address.model.person.UniquePersonList; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.reminder.UniqueReminderList; +import seedu.address.model.reminder.exceptions.DuplicateReminderException; +import seedu.address.model.reminder.exceptions.ReminderNotFoundException; import seedu.address.model.tag.Tag; import seedu.address.model.tag.UniqueTagList; @@ -25,7 +38,10 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; + private final UniqueCcaList ccas; private final UniqueTagList tags; + private final UniqueGoalList goals; + private final UniqueReminderList reminders; /* * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication @@ -36,13 +52,16 @@ public class AddressBook implements ReadOnlyAddressBook { */ { persons = new UniquePersonList(); + ccas = new UniqueCcaList(); tags = new UniqueTagList(); + goals = new UniqueGoalList(); + reminders = new UniqueReminderList(); } public AddressBook() {} /** - * Creates an AddressBook using the Persons and Tags in the {@code toBeCopied} + * Creates a CollegeZone using the Persons, Ccas, Tags and Goals in the {@code toBeCopied} */ public AddressBook(ReadOnlyAddressBook toBeCopied) { this(); @@ -54,39 +73,77 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) { public void setPersons(List persons) throws DuplicatePersonException { this.persons.setPersons(persons); } + //@@author sham-sheer + public void sortPersons(Index index) throws IndexOutOfBoundsException { + this.persons.sortPersons(index); + } + + //@@author deborahlow97 + public void setCcas(Set ccas) { + this.ccas.setCcas(ccas); } + //@@author public void setTags(Set tags) { this.tags.setTags(tags); } + //@@author deborahlow97 + public void setGoals(List goals) throws DuplicateGoalException { + this.goals.setGoals(goals); + } + + + public void setReminders(List reminders) throws DuplicateReminderException { + this.reminders.setReminders(reminders); + } + + //@@author + /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); + setCcas(new HashSet<>(newData.getCcaList())); setTags(new HashSet<>(newData.getTagList())); List syncedPersonList = newData.getPersonList().stream() + .map(this::syncWithMasterCcaList) .map(this::syncWithMasterTagList) .collect(Collectors.toList()); try { setPersons(syncedPersonList); } catch (DuplicatePersonException e) { - throw new AssertionError("AddressBooks should not have duplicate persons"); + throw new AssertionError("CollegeZone should not have duplicate persons"); + } + + List syncedGoalList = newData.getGoalList().stream().collect(Collectors.toList()); + try { + setGoals(syncedGoalList); + } catch (DuplicateGoalException e) { + throw new AssertionError("Goal Page should not have duplicate goals"); + } + + List syncedReminderList = newData.getReminderList().stream().collect(Collectors.toList()); + try { + setReminders(syncedReminderList); + } catch (DuplicateReminderException e) { + throw new AssertionError("Reminder list should not have duplicate reminders"); } } //// person-level operations /** - * Adds a person to the address book. - * Also checks the new person's tags and updates {@link #tags} with any new tags found, - * and updates the Tag objects in the person to point to those in {@link #tags}. + * Adds a person to CollegeZone. + * Also checks the new person's ccas and tags, and updates {@link #ccas #tags} with any new cca and tags found, + * and updates the Cca and Tag objects in the person to point to those in {@link #ccas #tags}. * * @throws DuplicatePersonException if an equivalent person already exists. */ public void addPerson(Person p) throws DuplicatePersonException { - Person person = syncWithMasterTagList(p); + Person person = syncWithMasterCcaList(p); + person = syncWithMasterTagList(person); // TODO: the tags master list will be updated even though the below line fails. // This can cause the tags master list to have additional tags that are not tagged to any person // in the person list. @@ -95,25 +152,77 @@ public void addPerson(Person p) throws DuplicatePersonException { /** * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code AddressBook}'s tag list will be updated with the tags of {@code editedPerson}. + * {@code CollegeZone}'s cca and tag list will be updated with the ccas and tags of {@code editedPerson}. * * @throws DuplicatePersonException if updating the person's details causes the person to be equivalent to * another existing person in the list. * @throws PersonNotFoundException if {@code target} could not be found in the list. * + * @see #syncWithMasterCcaList(Person) * @see #syncWithMasterTagList(Person) */ public void updatePerson(Person target, Person editedPerson) throws DuplicatePersonException, PersonNotFoundException { requireNonNull(editedPerson); - - Person syncedEditedPerson = syncWithMasterTagList(editedPerson); + Person syncedEditedPerson = syncWithMasterCcaList(editedPerson); + syncedEditedPerson = syncWithMasterTagList(syncedEditedPerson); // TODO: the tags master list will be updated even though the below line fails. // This can cause the tags master list to have additional tags that are not tagged to any person // in the person list. persons.setPerson(target, syncedEditedPerson); + removeUnusedCcas(); + removeUnusedTags(); } + //@@author deborahlow97 + /** + * Removes all {@code Ccas}s that are not used by any {@code Person} in this {@code AddressBook}. + */ + private void removeUnusedCcas() { + Set ccasInPersons = persons.asObservableList().stream() + .map(Person::getCcas) + .flatMap(Set::stream) + .collect(Collectors.toSet()); + ccas.setCcas(ccasInPersons); + } + + //@@author + /** + * Removes all {@code Tag}s that are not used by any {@code Person} in this {@code AddressBook}. + */ + private void removeUnusedTags() { + Set tagsInPersons = persons.asObservableList().stream() + .map(Person::getTags) + .flatMap(Set::stream) + .collect(Collectors.toSet()); + tags.setTags(tagsInPersons); + } + + //@@author deborahlow97 + /** + * Updates the master cca list to include ccas in {@code person} that are not in the list. + * @return a copy of this {@code person} such that every cca in this person points to a Cca object in the master + * list. + */ + private Person syncWithMasterCcaList(Person person) { + final UniqueCcaList personCcas = new UniqueCcaList(person.getCcas()); + ccas.mergeFrom(personCcas); + + // Create map with values = cca object references in the master list + // used for checking person cca references + final Map masterCcaObjects = new HashMap<>(); + ccas.forEach(cca -> masterCcaObjects.put(cca, cca)); + + // Rebuild the list of person tags to point to the relevant tags in the master tag list. + final Set correctCcaReferences = new HashSet<>(); + personCcas.forEach(cca -> correctCcaReferences.add(masterCcaObjects.get(cca))); + return new Person( + person.getName(), person.getPhone(), person.getBirthday(), + person.getLevelOfFriendship(), person.getUnitNumber(), correctCcaReferences, person.getMeetDate(), + person.getTags()); + } + + //@@author /** * Updates the master tag list to include tags in {@code person} that are not in the list. * @return a copy of this {@code person} such that every tag in this person points to a Tag object in the master @@ -132,7 +241,9 @@ private Person syncWithMasterTagList(Person person) { final Set correctTagReferences = new HashSet<>(); personTags.forEach(tag -> correctTagReferences.add(masterTagObjects.get(tag))); return new Person( - person.getName(), person.getPhone(), person.getEmail(), person.getAddress(), correctTagReferences); + person.getName(), person.getPhone(), person.getBirthday(), + person.getLevelOfFriendship(), person.getUnitNumber(), person.getCcas(), person.getMeetDate(), + correctTagReferences); } /** @@ -147,17 +258,174 @@ public boolean removePerson(Person key) throws PersonNotFoundException { } } + /** + * Removes {@code meet} from {@code person} in this {@code AddressBook}. + * @throws PersonNotFoundException if the {@code person} is not in this {@code AddressBook}. + */ + public void removeMeetFromPerson(Person person) throws PersonNotFoundException { + Meet newMeetDate = new Meet(""); + + Person newPerson = new Person(person.getName(), person.getPhone(), person.getBirthday(), + person.getLevelOfFriendship(), person.getUnitNumber(), person.getCcas(), newMeetDate, person.getTags()); + + try { + updatePerson(person, newPerson); + } catch (DuplicatePersonException dpe) { + throw new AssertionError("Modifying a person's meeting date only should not result in a duplicate. " + + "See Person#equals(Object)."); + } + } + + //// cca-level operations + + //@@author deborahlow97 + public void addCca(Cca cca) throws UniqueCcaList.DuplicateCcaException { + ccas.add(cca); + } + //// tag-level operations + //@@author public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { tags.add(t); } + /** + * Removes {@code tag} from all persons in this {@code AddressBook}. + */ + public void removeTag(Tag tag) { + try { + for (Person person : persons) { + removeTagFromPerson(tag, person); + } + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("Impossible: original person is obtained from the address book."); + } + } + + /** + * Removes {@code tag} from {@code person} in this {@code AddressBook}. + * @throws PersonNotFoundException if the {@code person} is not in this {@code AddressBook}. + */ + private void removeTagFromPerson(Tag tag, Person person) throws PersonNotFoundException { + Set newTags = new HashSet<>(person.getTags()); + + if (!newTags.remove(tag)) { + return; + } + + Person newPerson = + new Person(person.getName(), person.getPhone(), person.getBirthday(), person.getLevelOfFriendship(), + person.getUnitNumber(), person.getCcas(), person.getMeetDate(), newTags); + try { + updatePerson(person, newPerson); + } catch (DuplicatePersonException dpe) { + throw new AssertionError("Modifying a person's tags only should not result in a duplicate. " + + "See Person#equals(Object)."); + } + } + //// goal-level operations + + //@@author deborahlow97 + /** + * Adds a goal to CollegeZone. + * @throws DuplicateGoalException if an equivalent goal already exists. + */ + public void addGoal(Goal g) throws DuplicateGoalException { + goals.add(g); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * @throws GoalNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removeGoal(Goal key) throws GoalNotFoundException { + if (goals.remove(key)) { + return true; + } else { + throw new GoalNotFoundException(); + } + } + + /** + * Replaces the given goal {@code target} in the list with {@code editedGoal}. + * + * @throws DuplicateGoalException if updating the goal's details causes the goal to be equivalent to + * another existing goal in the list. + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + public void updateGoal(Goal target, Goal editedGoal) + throws DuplicateGoalException, GoalNotFoundException { + requireNonNull(editedGoal); + goals.setGoal(target, editedGoal); + } + + /** + * Replaces the given goal {@code target} in the list with {@code editedGoal}. + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + public void updateGoalWithoutParameters(Goal target, Goal editedGoal) throws GoalNotFoundException { + requireNonNull(editedGoal); + goals.setGoalWithoutParameters(target, editedGoal); + } + + /** + * Sorts goal based on the sort field and sort order input. + */ + public void sortGoal(String sortField, String sortOrder) throws EmptyGoalListException { + requireNonNull(sortField); + requireNonNull(sortOrder); + if (goals.getSize() > 0) { + goals.sortGoal(sortField, sortOrder); + } else { + throw new EmptyGoalListException(); + } + } + //// reminder-level operations + + //@@author fuadsahmawi + /** + * Adds a reminder to CollegeZone. + * @throws DuplicateReminderException if an equivalent reminder already exists. + */ + public void addReminder (Reminder r) throws DuplicateReminderException { + reminders.add(r); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * @throws ReminderNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removeReminder(Reminder key) throws ReminderNotFoundException { + if (reminders.remove(key)) { + return true; + } else { + throw new ReminderNotFoundException(); + } + } + /** + * Replaces the given reminder {@code target} in the list with {@code editedReminder}. + * + * @throws DuplicateReminderException if updating the reminder's details causes the reminder to be equivalent to + * another existing reminder in the list. + * @throws ReminderNotFoundException if {@code target} could not be found in the list. + */ + public void updateReminder(Reminder target, Reminder editedReminder) + throws DuplicateReminderException, ReminderNotFoundException { + requireNonNull(editedReminder); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any person + // in the person list. + reminders.setReminder(target, editedReminder); + } + + //@@author //// util methods @Override public String toString() { - return persons.asObservableList().size() + " persons, " + tags.asObservableList().size() + " tags"; + return persons.asObservableList().size() + " persons, " + ccas.asObservableList().size() + + "ccas, " + tags.asObservableList().size() + " tags"; // TODO: refine later } @@ -166,22 +434,38 @@ public ObservableList getPersonList() { return persons.asObservableList(); } + @Override + public ObservableList getCcaList() { + return ccas.asObservableList(); + } + @Override public ObservableList getTagList() { return tags.asObservableList(); } + @Override + public ObservableList getGoalList() { + return goals.asObservableList(); + } + + @Override + public ObservableList getReminderList() { + return reminders.asObservableList(); + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof AddressBook // instanceof handles nulls && this.persons.equals(((AddressBook) other).persons) + && this.ccas.equalsOrderInsensitive(((AddressBook) other).ccas) && this.tags.equalsOrderInsensitive(((AddressBook) other).tags)); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(persons, tags); + return Objects.hash(persons, ccas, tags); } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index 4a6079ce0199..ff60726cd35b 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -3,9 +3,19 @@ import java.util.function.Predicate; import javafx.collections.ObservableList; +import seedu.address.commons.core.index.Index; +import seedu.address.model.goal.Goal; +import seedu.address.model.goal.exceptions.DuplicateGoalException; +import seedu.address.model.goal.exceptions.EmptyGoalListException; +import seedu.address.model.goal.exceptions.GoalNotFoundException; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.reminder.exceptions.DuplicateReminderException; +import seedu.address.model.reminder.exceptions.ReminderNotFoundException; +import seedu.address.model.tag.Tag; + /** * The API of the Model component. @@ -14,6 +24,12 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_GOALS = unused -> true; + + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_REMINDERS = unused -> true; + /** Clears existing backing model and replaces with the provided new data. */ void resetData(ReadOnlyAddressBook newData); @@ -23,6 +39,12 @@ public interface Model { /** Deletes the given person. */ void deletePerson(Person target) throws PersonNotFoundException; + /** Sorts a list according to the given type index */ + void sortPersons(Index index) throws IndexOutOfBoundsException; + + /** Deletes the given person's meet date */ + void deleteMeetDate(Person target) throws PersonNotFoundException; + /** Adds the given person */ void addPerson(Person person) throws DuplicatePersonException; @@ -45,4 +67,61 @@ void updatePerson(Person target, Person editedPerson) */ void updateFilteredPersonList(Predicate predicate); + /** Removes the given {@code tag} from all {@code Person}s. */ + void deleteTag(Tag tag); + + //@@author deborahlow97 + /** Adds the given goal */ + void addGoal(Goal goal) throws DuplicateGoalException; + + /** Returns an unmodifiable view of the filtered goal list */ + ObservableList getFilteredGoalList(); + + /** + * Replaces the given goal {@code target} with {@code editedGoal}. + * + * @throws DuplicateGoalException if updating the goal's details causes the goal to be equivalent to + * another existing goal in the list. + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + void updateGoal(Goal target, Goal editedGoal) + throws DuplicateGoalException, GoalNotFoundException; + + /** + * Updates the filter of the filtered goal list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredGoalList(Predicate predicate); + + /** Deletes the given goal. */ + void deleteGoal(Goal target) throws GoalNotFoundException; + + /** + * Replaces the given goal {@code target} with {@code updateGoal}. + * + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + void updateGoalWithoutParameters(Goal target, Goal editedGoal) throws GoalNotFoundException; + + /** + * Sort the goal based on sortType + */ + void sortGoal(String sortType, String sortOrder) throws EmptyGoalListException; + + //@@author + // fuadsahmawi + /** Adds the given reminder. */ + void addReminder(Reminder reminder) throws DuplicateReminderException; + + /** Returns an unmodifiable view of the filtered reminder list */ + ObservableList getFilteredReminderList(); + + /** + * Updates the filter of the filtered reminder list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredReminderList(Predicate predicate); + + /** Deletes the given reminder. */ + void deleteReminder(Reminder target) throws ReminderNotFoundException; } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 22a7d0eb3f4d..9977e5e9a3c5 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -11,10 +11,19 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.index.Index; import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.model.goal.Goal; +import seedu.address.model.goal.exceptions.DuplicateGoalException; +import seedu.address.model.goal.exceptions.EmptyGoalListException; +import seedu.address.model.goal.exceptions.GoalNotFoundException; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.reminder.exceptions.DuplicateReminderException; +import seedu.address.model.reminder.exceptions.ReminderNotFoundException; +import seedu.address.model.tag.Tag; /** * Represents the in-memory model of the address book data. @@ -25,6 +34,8 @@ public class ModelManager extends ComponentManager implements Model { private final AddressBook addressBook; private final FilteredList filteredPersons; + private final FilteredList filteredGoals; + private final FilteredList filteredReminders; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -37,6 +48,8 @@ public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { this.addressBook = new AddressBook(addressBook); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredGoals = new FilteredList<>(this.addressBook.getGoalList()); + filteredReminders = new FilteredList<>(this.addressBook.getReminderList()); } public ModelManager() { @@ -81,12 +94,35 @@ public void updatePerson(Person target, Person editedPerson) indicateAddressBookChanged(); } + + @Override + public void deleteTag (Tag t) { + addressBook.removeTag(t); + } + + + //@@author sham-sheer + @Override + public void deleteMeetDate (Person person) throws PersonNotFoundException { + addressBook.removeMeetFromPerson(person); + indicateAddressBookChanged(); + } + + @Override + public synchronized void sortPersons(Index index) throws IndexOutOfBoundsException { + addressBook.sortPersons(index); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + indicateAddressBookChanged(); + } + + //=========== Filtered Person List Accessors ============================================================= /** * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of * {@code addressBook} */ + //@@author @Override public ObservableList getFilteredPersonList() { return FXCollections.unmodifiableObservableList(filteredPersons); @@ -116,4 +152,88 @@ public boolean equals(Object obj) { && filteredPersons.equals(other.filteredPersons); } + //@@author deborahlow97 + @Override + public void addGoal(Goal goal) throws DuplicateGoalException { + addressBook.addGoal(goal); + updateFilteredGoalList(PREDICATE_SHOW_ALL_GOALS); + indicateAddressBookChanged(); + } + + @Override + public ObservableList getFilteredGoalList() { + return FXCollections.unmodifiableObservableList(filteredGoals); + } + + @Override + public void updateFilteredGoalList(Predicate predicate) { + requireNonNull(predicate); + filteredGoals.setPredicate(predicate); + } + + @Override + public synchronized void deleteGoal(Goal target) throws GoalNotFoundException { + addressBook.removeGoal(target); + indicateAddressBookChanged(); + } + + @Override + public void updateGoal(Goal target, Goal editedGoal) + throws DuplicateGoalException, GoalNotFoundException { + requireAllNonNull(target, editedGoal); + + addressBook.updateGoal(target, editedGoal); + indicateAddressBookChanged(); + } + + @Override + public void updateGoalWithoutParameters(Goal target, Goal editedGoal) + throws GoalNotFoundException { + requireAllNonNull(target, editedGoal); + + addressBook.updateGoalWithoutParameters(target, editedGoal); + indicateAddressBookChanged(); + } + + @Override + public void sortGoal(String sortGoalType, String sortGoalOrder) throws EmptyGoalListException { + requireAllNonNull(sortGoalType, sortGoalOrder); + addressBook.sortGoal(sortGoalType, sortGoalOrder); + indicateAddressBookChanged(); + } + + //@@author fuadsahmawi + @Override + public void addReminder(Reminder reminder) throws DuplicateReminderException { + addressBook.addReminder(reminder); + updateFilteredReminderList(PREDICATE_SHOW_ALL_REMINDERS); + indicateAddressBookChanged(); + } + + @Override + public ObservableList getFilteredReminderList() { + return FXCollections.unmodifiableObservableList(filteredReminders); + } + + @Override + public void updateFilteredReminderList(Predicate predicate) { + requireNonNull(predicate); + filteredReminders.setPredicate(predicate); + } + + @Override + public synchronized void deleteReminder(Reminder reminder) throws ReminderNotFoundException { + addressBook.removeReminder(reminder); + indicateAddressBookChanged(); + } + /* + @Override + public void updateReminder(Reminder target, Reminder editedReminder) + throws DuplicateReminderException, ReminderNotFoundException { + requireAllNonNull(target, editedReminder); + + addressBook.updateReminder(target, editedReminder); + indicateAddressBookChanged(); + } + */ } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 1f4e49a37d67..f271bd606ec3 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,7 +1,11 @@ package seedu.address.model; import javafx.collections.ObservableList; + +import seedu.address.model.goal.Goal; +import seedu.address.model.person.Cca; import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; import seedu.address.model.tag.Tag; /** @@ -15,10 +19,31 @@ public interface ReadOnlyAddressBook { */ ObservableList getPersonList(); + //@@author deborahlow97 + /** + * Returns an unmodifiable view of the ccas list. + * This list will not contain any duplicate ccas. + */ + ObservableList getCcaList(); + + //@@author /** * Returns an unmodifiable view of the tags list. * This list will not contain any duplicate tags. */ ObservableList getTagList(); + //@@author deborahlow97 + /** + * Returns an unmodifiable view of the goals list. + * This list will not contain any duplicate goals. + */ + ObservableList getGoalList(); + //@@author fuadsahmawi + /** + * Returns an unmodifiable view of the reminders list. + * This list will not contain any duplicate reminders. + */ + ObservableList getReminderList(); + } diff --git a/src/main/java/seedu/address/model/ThemeColourUtil.java b/src/main/java/seedu/address/model/ThemeColourUtil.java new file mode 100644 index 000000000000..802b3c64cfd3 --- /dev/null +++ b/src/main/java/seedu/address/model/ThemeColourUtil.java @@ -0,0 +1,23 @@ +package seedu.address.model; + +import java.util.HashMap; + +//@@author deborahlow97 +/** + * Contains utility methods for ThemeColour. + */ +public class ThemeColourUtil { + + private static final HashMap themes; + + static { + themes = new HashMap<>(); + themes.put("light", "view/LightTheme.css"); + themes.put("bubblegum", "view/BubblegumTheme.css"); + themes.put("dark", "view/DarkTheme.css"); + } + + public static HashMap getThemeHashMap() { + return themes; + } +} diff --git a/src/main/java/seedu/address/model/goal/Completion.java b/src/main/java/seedu/address/model/goal/Completion.java new file mode 100644 index 000000000000..bc8d54299009 --- /dev/null +++ b/src/main/java/seedu/address/model/goal/Completion.java @@ -0,0 +1,45 @@ +package seedu.address.model.goal; + +import static java.util.Objects.requireNonNull; + +//@@author deborahlow97 +/** + * Represents a Goal's completion status in the Goals Page. + */ +public class Completion { + public final String value; + public final boolean hasCompleted; + + /** + * Constructs a {@code Completion}. + * + * @param isCompleted A valid boolean. + */ + public Completion(Boolean isCompleted) { + requireNonNull(isCompleted); + if (isCompleted) { + this.hasCompleted = true; + this.value = "true"; + } else { + this.hasCompleted = false; + this.value = "false"; + } + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Completion // instanceof handles nulls + && this.value.equals(((Completion) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/goal/EndDateTime.java b/src/main/java/seedu/address/model/goal/EndDateTime.java new file mode 100644 index 000000000000..7aac3ef05375 --- /dev/null +++ b/src/main/java/seedu/address/model/goal/EndDateTime.java @@ -0,0 +1,50 @@ +package seedu.address.model.goal; + +import static seedu.address.logic.parser.DateTimeParser.nattyDateAndTimeParser; +import static seedu.address.logic.parser.DateTimeParser.properDateTimeFormat; + +import java.time.LocalDateTime; + +//@@author deborahlow97 +/** + * Represents a Goal's end date and time in the Goals Page. + * Guarantees: immutable; is valid + */ +public class EndDateTime { + + public final String value; + public final LocalDateTime localDateTimeValue; + /** + * Constructs a {@code EndDateTime}. + * + * @param endDateTime A valid endDateTime number. + */ + public EndDateTime(String endDateTime) { + if (endDateTime.equals("")) { + this.value = ""; + this.localDateTimeValue = null; + } else { + LocalDateTime localEndDateTime = nattyDateAndTimeParser(endDateTime).get(); + this.value = properDateTimeFormat(localEndDateTime); + this.localDateTimeValue = localEndDateTime; + } + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EndDateTime // instanceof handles nulls + && this.value.equals(((EndDateTime) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/goal/Goal.java b/src/main/java/seedu/address/model/goal/Goal.java new file mode 100644 index 000000000000..5c900797a54c --- /dev/null +++ b/src/main/java/seedu/address/model/goal/Goal.java @@ -0,0 +1,88 @@ +package seedu.address.model.goal; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Objects; + +//@@author deborahlow97 +/** + * Represents a Goal in the Goals Page. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Goal { + + private final Importance importance; + private final GoalText goalText; + private final StartDateTime startDateTime; + private final EndDateTime endDateTime; + private final Completion completion; + + /** + * Every field must be present and not null. + */ + + public Goal(Importance importance, GoalText goalText, StartDateTime startDateTime, + EndDateTime endDateTime, Completion completion) { + requireAllNonNull(importance, goalText, startDateTime, completion); + this.importance = importance; + this.goalText = goalText; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.completion = completion; + } + + public Importance getImportance() { + return importance; + } + + public GoalText getGoalText() { + return goalText; + } + + public StartDateTime getStartDateTime() { + return startDateTime; + } + + public EndDateTime getEndDateTime() { + return endDateTime; + } + + public Completion getCompletion() { + return completion; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Goal)) { + return false; + } + + Goal otherPerson = (Goal) other; + return otherPerson.getImportance().equals(this.getImportance()) + && otherPerson.getGoalText().equals(this.getGoalText()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(importance, goalText, startDateTime); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(" Importance: ") + .append(getImportance()) + .append(" Goal Text: ") + .append(getGoalText()) + .append(" Start Date Time: ") + .append(getStartDateTime()); + + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/goal/GoalText.java b/src/main/java/seedu/address/model/goal/GoalText.java new file mode 100644 index 000000000000..f7c745b539fc --- /dev/null +++ b/src/main/java/seedu/address/model/goal/GoalText.java @@ -0,0 +1,54 @@ +package seedu.address.model.goal; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author deborahlow97 +/** + * Represents a Goal's text in the Goals Page. + * Guarantees: immutable; is valid as declared in {@link #isValidGoalText(String)} + */ +public class GoalText { + + + public static final String MESSAGE_GOAL_TEXT_CONSTRAINTS = + "Goal text can be any expression that are not just whitespaces."; + public static final String GOAL_TEXT_VALIDATION_REGEX = "^(?!\\s*$).+"; + public final String value; + + /** + * Constructs a {@code GoalText}. + * + * @param goalText A valid goal text. + */ + public GoalText(String goalText) { + requireNonNull(goalText); + checkArgument(isValidGoalText(goalText), MESSAGE_GOAL_TEXT_CONSTRAINTS); + this.value = goalText; + } + + /** + * Returns true if a given string is a valid goal text. + */ + public static boolean isValidGoalText(String test) { + return test.matches(GOAL_TEXT_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GoalText // instanceof handles nulls + && this.value.equals(((GoalText) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/goal/Importance.java b/src/main/java/seedu/address/model/goal/Importance.java new file mode 100644 index 000000000000..5656eb8d13de --- /dev/null +++ b/src/main/java/seedu/address/model/goal/Importance.java @@ -0,0 +1,80 @@ +package seedu.address.model.goal; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author deborahlow97 +/** + * Represents a Goal's importance in CollegeZone. + * Guarantees: immutable; is valid as declared in {@link #isValidImportance(String)} + */ +public class Importance implements Comparable { + + + public static final String MESSAGE_IMPORTANCE_CONSTRAINTS = + "Importance should only be a numerical integer value between 1 to 10."; + public static final String IMPORTANCE_VALIDATION_REGEX = "[0-9]+"; + private static final int MINIMUM_IMPORTANCE = 1; + private static final int MAXIMUM_IMPORTANCE = 10; + private static int importanceInIntegerForm; + public final String value; + + /** + * Constructs a {@code Importance}. + * + * @param importance A valid importance. + */ + public Importance(String importance) { + requireNonNull(importance); + checkArgument(isValidImportance(importance), MESSAGE_IMPORTANCE_CONSTRAINTS); + this.value = importance; + } + + /** + * Returns true if a given string is a valid goal importance. + */ + public static boolean isValidImportance(String test) { + return test.matches(IMPORTANCE_VALIDATION_REGEX) && isAnIntegerWithinRange(test); + } + + /** + * Returns true if a given string is an integer and within range of importance. + */ + private static boolean isAnIntegerWithinRange(String test) { + importanceInIntegerForm = Integer.parseInt(test); + if (importanceInIntegerForm >= MINIMUM_IMPORTANCE + && importanceInIntegerForm <= MAXIMUM_IMPORTANCE) { + return true; + } else { + return false; + } + } + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Importance // instanceof handles nulls + && this.value.equals(((Importance) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public int compareTo(Importance importance) { + if ((Integer.valueOf(importance.value)).equals(Integer.valueOf(this.value))) { + return 0; + } else if ((Integer.valueOf(importance.value)) < (Integer.valueOf(this.value))) { + return 1; + } else if ((Integer.valueOf(importance.value)) > (Integer.valueOf(this.value))) { + return -1; + } + return 0; + } +} diff --git a/src/main/java/seedu/address/model/goal/StartDateTime.java b/src/main/java/seedu/address/model/goal/StartDateTime.java new file mode 100644 index 000000000000..37eb1c1182ce --- /dev/null +++ b/src/main/java/seedu/address/model/goal/StartDateTime.java @@ -0,0 +1,62 @@ +package seedu.address.model.goal; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.DateTimeParser.getLocalDateTimeFromProperDateTime; +import static seedu.address.logic.parser.DateTimeParser.properDateTimeFormat; + +import java.time.LocalDateTime; + +//@@author deborahlow97 +/** + * Represents a Goal's start date in the address book. + */ +public class StartDateTime implements Comparable { + + public final String value; + public final LocalDateTime localDateTimeValue; + + + /** + * Constructs a {@code StartDateTime}. + * + * @param startDateTime A valid LocalDateTime. + */ + public StartDateTime(LocalDateTime startDateTime) { + requireNonNull(startDateTime); + this.localDateTimeValue = startDateTime; + this.value = properDateTimeFormat(startDateTime); + } + + public StartDateTime(String startDateTimeInString) { + requireNonNull(startDateTimeInString); + this.value = startDateTimeInString; + this.localDateTimeValue = getLocalDateTimeFromProperDateTime(startDateTimeInString); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof StartDateTime // instanceof handles nulls + && this.value.equals(((StartDateTime) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public int compareTo(StartDateTime startDateTime) { + if ((startDateTime.localDateTimeValue).isBefore(this.localDateTimeValue)) { + return 1; + } else if ((startDateTime.localDateTimeValue).isAfter(this.localDateTimeValue)) { + return -1; + } + return 0; + } +} diff --git a/src/main/java/seedu/address/model/goal/UniqueGoalList.java b/src/main/java/seedu/address/model/goal/UniqueGoalList.java new file mode 100644 index 000000000000..29f821052477 --- /dev/null +++ b/src/main/java/seedu/address/model/goal/UniqueGoalList.java @@ -0,0 +1,181 @@ +package seedu.address.model.goal; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.model.goal.exceptions.DuplicateGoalException; +import seedu.address.model.goal.exceptions.EmptyGoalListException; +import seedu.address.model.goal.exceptions.GoalNotFoundException; + +//@@author deborahlow97 +/** + * A list of goals that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Goal#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class UniqueGoalList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent goal as the given argument. + */ + public boolean contains(Goal toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a goal to the list. + * + * @throws DuplicateGoalException if the goal to add is a duplicate of an existing goal in the list. + */ + public void add(Goal toAdd) throws DuplicateGoalException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateGoalException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the goal {@code target} in the list with {@code editedGoal}. + * + * @throws DuplicateGoalException if the replacement is equivalent to another existing goal in the list. + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + public void setGoal(Goal target, Goal editedGoal) + throws DuplicateGoalException, GoalNotFoundException { + requireNonNull(editedGoal); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new GoalNotFoundException(); + } + + if (!target.equals(editedGoal) && internalList.contains(editedGoal)) { + throw new DuplicateGoalException(); + } + + internalList.set(index, editedGoal); + } + + /** + * Replaces the goal {@code target} in the list with {@code editedGoal}. + * @throws GoalNotFoundException if {@code target} could not be found in the list. + */ + public void setGoalWithoutParameters(Goal target, Goal editedGoal) + throws GoalNotFoundException { + requireNonNull(editedGoal); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new GoalNotFoundException(); + } + + internalList.set(index, editedGoal); + } + + /** + * Removes the equivalent goal from the list. + * + * @throws GoalNotFoundException if no such goal could be found in the list. + */ + public boolean remove(Goal toRemove) throws GoalNotFoundException { + requireNonNull(toRemove); + final boolean goalFoundAndDeleted = internalList.remove(toRemove); + if (!goalFoundAndDeleted) { + throw new GoalNotFoundException(); + } + return goalFoundAndDeleted; + } + + public void setGoals(UniqueGoalList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setGoals(List goals) throws DuplicateGoalException { + requireAllNonNull(goals); + final UniqueGoalList replacement = new UniqueGoalList(); + for (final Goal goal : goals) { + replacement.add(goal); + } + setGoals(replacement); + } + + /** + * Returns the size of goal list. + */ + public int getSize() { + return internalList.size(); + } + + /** + * Sort goals internal list using comparator + * @param sortField + */ + public void sortGoal(String sortField, String sortOrder) throws EmptyGoalListException { + String sortFieldAndOrder = sortField + " " + sortOrder; + switch (sortFieldAndOrder) { + case "importance ascending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) ->goalA.getImportance() + .compareTo(goalB.getImportance())); + break; + case "importance descending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) ->goalB.getImportance() + .compareTo(goalA.getImportance())); + break; + case "completion ascending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) -> new Boolean(goalA.getCompletion().hasCompleted) + .compareTo(goalB.getCompletion().hasCompleted)); + break; + case "completion descending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) -> new Boolean(goalB.getCompletion().hasCompleted) + .compareTo(goalA.getCompletion().hasCompleted)); + break; + case "startdatetime ascending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) ->goalA.getStartDateTime() + .compareTo(goalB.getStartDateTime())); + break; + case "startdatetime descending": + FXCollections.sort(internalList, (Goal goalA, Goal goalB) ->goalB.getStartDateTime() + .compareTo(goalA.getStartDateTime())); + break; + + default: + break; + } + } + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueGoalList // instanceof handles nulls + && this.internalList.equals(((UniqueGoalList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/goal/exceptions/DuplicateGoalException.java b/src/main/java/seedu/address/model/goal/exceptions/DuplicateGoalException.java new file mode 100644 index 000000000000..6179cc4e541c --- /dev/null +++ b/src/main/java/seedu/address/model/goal/exceptions/DuplicateGoalException.java @@ -0,0 +1,13 @@ +package seedu.address.model.goal.exceptions; + +import seedu.address.commons.exceptions.DuplicateDataException; + +//@@author deborahlow97 +/** + * Signals that the operation will result in duplicate Goal objects. + */ +public class DuplicateGoalException extends DuplicateDataException { + public DuplicateGoalException() { + super("Operation would result in duplicate goals"); + } +} diff --git a/src/main/java/seedu/address/model/goal/exceptions/EmptyGoalListException.java b/src/main/java/seedu/address/model/goal/exceptions/EmptyGoalListException.java new file mode 100644 index 000000000000..d09913d69138 --- /dev/null +++ b/src/main/java/seedu/address/model/goal/exceptions/EmptyGoalListException.java @@ -0,0 +1,8 @@ +package seedu.address.model.goal.exceptions; + +//@@author deborahlow97 +/** + * Signals that the current goal list is empty. + */ +public class EmptyGoalListException extends Exception { +} diff --git a/src/main/java/seedu/address/model/goal/exceptions/GoalNotFoundException.java b/src/main/java/seedu/address/model/goal/exceptions/GoalNotFoundException.java new file mode 100644 index 000000000000..50df04ce4aec --- /dev/null +++ b/src/main/java/seedu/address/model/goal/exceptions/GoalNotFoundException.java @@ -0,0 +1,7 @@ +package seedu.address.model.goal.exceptions; + +//@@author deborahlow97 +/** + * Signals that the operation is unable to find the specified goal. + */ +public class GoalNotFoundException extends Exception {} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index 5e981f07790a..000000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,58 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_ADDRESS_CONSTRAINTS = - "Person addresses can take any values, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String ADDRESS_VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_ADDRESS_CONSTRAINTS); - this.value = address; - } - - /** - * Returns true if a given string is a valid person email. - */ - public static boolean isValidAddress(String test) { - return test.matches(ADDRESS_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && this.value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Birthday.java b/src/main/java/seedu/address/model/person/Birthday.java new file mode 100644 index 000000000000..fab9d2fc5902 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Birthday.java @@ -0,0 +1,142 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; + +import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.address.logic.parser.DateTimeParser.nattyDateAndTimeParser; + +import java.time.LocalDateTime; + +import java.util.Calendar; +import java.util.GregorianCalendar; + + + + +//@@author deborahlow97 +/** + * Represents a Person's birthday in CollegeZone. + * Guarantees: immutable; is valid as declared in {@link #isValidBirthday(String)} + */ +public class Birthday { + + public static final String MESSAGE_BIRTHDAY_CONSTRAINTS = "Person birthday should be a valid date and cannot " + + "be later than today's date."; + public static final String BIRTHDAY_VALIDATION_REGEX = + "^(((0[1-9]|[12]\\d|3[01])\\/(0[13578]|1[02])\\/((19|[2-9]\\d)\\d{2}))|((0[1-9]|[12]\\d|30)" + + "\\/(0[13456789]|1[012])\\/((19|[2-9]\\d)\\d{2}))|((0[1-9]|1\\d|2[0-8])\\/02\\/((19|[2-9]\\d)" + + "\\d{2}))|(29\\/02\\/((1[6-9]|[2-9]\\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|" + + "[3579][26])00))))$"; + + public final String value; + + /** + * Constructs an {@code Birthday}. + * + * @param birthday A valid birthday. + */ + public Birthday(String birthday) { + requireNonNull(birthday); + checkArgument(isValidBirthday(birthday), MESSAGE_BIRTHDAY_CONSTRAINTS); + this.value = birthday; + } + + /** + * Returns if a given string is a valid person birthday (before current date). + */ + public static boolean isValidBirthday(String test) { + LocalDateTime birthdayLocalDateTime = null; + LocalDateTime currentLocalDateTime = LocalDateTime.now(); + if (test.matches(BIRTHDAY_VALIDATION_REGEX)) { + String birthdayInDifferentFormat = getDifferentBirthdayFormat(test); + birthdayLocalDateTime = nattyDateAndTimeParser(birthdayInDifferentFormat).get(); + } else { + return false; + } + return isBeforeCurrentDate(birthdayLocalDateTime, currentLocalDateTime); + } + + /** + * Takes in @param birthdayLocalDateTime and @param currentLocalDateTime and checks if 1st parameter is later + * than the second parameter + * @return boolean + */ + private static boolean isBeforeCurrentDate(LocalDateTime birthdayLocalDateTime, + LocalDateTime currentLocalDateTime) { + if (birthdayLocalDateTime.isBefore(currentLocalDateTime)) { + return true; + } else { + return false; + } + } + /** + * + * Takes in @param date in dd/mm/yyyy format + * @return birthday string in mm/dd/yyyy format + */ + public static String getDifferentBirthdayFormat(String date) { + String day = date.substring(0, 2); + String month = date.substring(3, 5); + String year = date.substring(6, 10); + StringBuilder builder = new StringBuilder(); + builder.append(month) + .append("/") + .append(day) + .append("/") + .append(year); + return builder.toString(); + } + + //@@author sham-sheer + /** + * Converts Birth date to a time that is relative to current date, for sorting purposes + */ + public static long birthDateToInt(String date) { + Calendar calendar = Calendar.getInstance(); + long longDate = convertbirthDateToSeconds(date.toString()); + long currentDate = calendar.getTimeInMillis(); + long timeDiff = longDate - currentDate; + if (timeDiff < 0) { + return Long.MAX_VALUE; + } else { + return timeDiff; + } + } + + /** + * Converts Birth date to seconds + */ + public static long convertbirthDateToSeconds(String date) { + if (date == "") { + return 0; + } + int day = Integer.parseInt(date.toString().substring(0, + 2)); + int month = Integer.parseInt(date.toString().substring(3, + 5)); + int year = 2018; + Calendar calendar = new GregorianCalendar(); + calendar.set(year, month - 1, day); + long seconds = calendar.getTimeInMillis(); + return seconds; + } + + //@@author deborahlow97 + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Birthday // instanceof handles nulls + && this.value.equals(((Birthday) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Cca.java b/src/main/java/seedu/address/model/person/Cca.java new file mode 100644 index 000000000000..7442121ad5b3 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Cca.java @@ -0,0 +1,54 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author deborahlow97 +/** + * Represents a Person's CCAs in CollegeZone. + * Guarantees: immutable; is valid as declared in {@link #isValidCcaName(String)} + */ +public class Cca { + + public static final String MESSAGE_CCA_CONSTRAINTS = "CCAs should be in alphanumeric"; + public static final String CCA_VALIDATION_REGEX = "\\s*\\p{Alnum}[\\p{Alnum}\\s]*"; + public final String ccaName; + + /** + * Constructs a {@code CCA}. + * + * @param ccaName A valid CCA. + */ + public Cca(String ccaName) { + requireNonNull(ccaName); + checkArgument(isValidCcaName(ccaName), MESSAGE_CCA_CONSTRAINTS); + this.ccaName = ccaName; + } + + /** + * Returns true if a given string is a valid CCA name. + */ + public static boolean isValidCcaName(String test) { + return test.matches(CCA_VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Cca // instanceof handles nulls + && this.ccaName.equals(((Cca) other).ccaName)); // state check + } + + @Override + public int hashCode() { + return ccaName.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return '[' + ccaName + ']'; + } + +} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java deleted file mode 100644 index 3759a577ec59..000000000000 --- a/src/main/java/seedu/address/model/person/Email.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's email in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} - */ -public class Email { - - private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-"; - public static final String MESSAGE_EMAIL_CONSTRAINTS = "Person emails should be of the format local-part@domain " - + "and adhere to the following constraints:\n" - + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " - + "the parentheses, (" + SPECIAL_CHARACTERS + ") .\n" - + "2. This is followed by a '@' and then a domain name. " - + "The domain name must:\n" - + " - be at least 2 characters long\n" - + " - start and end with alphanumeric characters\n" - + " - consist of alphanumeric characters, a period or a hyphen for the characters in between, if any."; - // alphanumeric and special characters - private static final String LOCAL_PART_REGEX = "^[\\w" + SPECIAL_CHARACTERS + "]+"; - private static final String DOMAIN_FIRST_CHARACTER_REGEX = "[^\\W_]"; // alphanumeric characters except underscore - private static final String DOMAIN_MIDDLE_REGEX = "[a-zA-Z0-9.-]*"; // alphanumeric, period and hyphen - private static final String DOMAIN_LAST_CHARACTER_REGEX = "[^\\W_]$"; - public static final String EMAIL_VALIDATION_REGEX = LOCAL_PART_REGEX + "@" - + DOMAIN_FIRST_CHARACTER_REGEX + DOMAIN_MIDDLE_REGEX + DOMAIN_LAST_CHARACTER_REGEX; - - public final String value; - - /** - * Constructs an {@code Email}. - * - * @param email A valid email address. - */ - public Email(String email) { - requireNonNull(email); - checkArgument(isValidEmail(email), MESSAGE_EMAIL_CONSTRAINTS); - this.value = email; - } - - /** - * Returns if a given string is a valid person email. - */ - public static boolean isValidEmail(String test) { - return test.matches(EMAIL_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Email // instanceof handles nulls - && this.value.equals(((Email) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/LevelOfFriendship.java b/src/main/java/seedu/address/model/person/LevelOfFriendship.java new file mode 100644 index 000000000000..4c89de177a79 --- /dev/null +++ b/src/main/java/seedu/address/model/person/LevelOfFriendship.java @@ -0,0 +1,70 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author deborahlow97 +/** + * Represents a Person's Level of Friendship in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidLevelOfFriendship(String)} + */ +public class LevelOfFriendship { + + public static final String MESSAGE_LEVEL_OF_FRIENDSHIP_CONSTRAINTS = + "Level of Friendship should only be a numerical integer value between 1 to 10"; + public static final String LEVEL_OF_FRIENDSHIP_VALIDATION_REGEX = "[0-9]+"; + private static final int MINIMUM_LEVEL_OF_FRIENDSHIP = 1; + private static final int MAXIMUM_LEVEL_OF_FRIENDSHIP = 10; + private static int levelOfFriendshipInIntegerForm; + public final String value; + + /** + * * Constructs an {@code LevelOfFriendship}. + * + * @param levelOfFriendship A valid level of friendship number. + */ + public LevelOfFriendship(String levelOfFriendship) { + requireNonNull(levelOfFriendship); + checkArgument(isValidLevelOfFriendship(levelOfFriendship), MESSAGE_LEVEL_OF_FRIENDSHIP_CONSTRAINTS); + this.value = levelOfFriendship; + } + + /** + * Returns true if a given string is a valid person level of friendship. + */ + public static boolean isValidLevelOfFriendship(String test) { + + return test.matches(LEVEL_OF_FRIENDSHIP_VALIDATION_REGEX) && isAnIntegerWithinRange(test); + } + + /** + * Returns true if a given string is an integer and within range of level of friendship. + */ + private static boolean isAnIntegerWithinRange(String test) { + levelOfFriendshipInIntegerForm = Integer.parseInt(test); + if (levelOfFriendshipInIntegerForm >= MINIMUM_LEVEL_OF_FRIENDSHIP + && levelOfFriendshipInIntegerForm <= MAXIMUM_LEVEL_OF_FRIENDSHIP) { + return true; + } else { + return false; + } + } + + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LevelOfFriendship // instanceof handles nulls + && this.value.equals(((LevelOfFriendship) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/LofContainsValuePredicate.java b/src/main/java/seedu/address/model/person/LofContainsValuePredicate.java new file mode 100644 index 000000000000..47cc7149d1dc --- /dev/null +++ b/src/main/java/seedu/address/model/person/LofContainsValuePredicate.java @@ -0,0 +1,34 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +//@@author zuweitrack +/** + * Tests that a {@code Person}'s {@code UnitNumber} matches any of the keywords given. + */ +public class LofContainsValuePredicate implements Predicate { + private final List keywords; + + public LofContainsValuePredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase + (person.getLevelOfFriendship().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LofContainsValuePredicate // instanceof handles nulls + && this.keywords.equals(((LofContainsValuePredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/Meet.java b/src/main/java/seedu/address/model/person/Meet.java new file mode 100644 index 000000000000..99f7cf21c115 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Meet.java @@ -0,0 +1,94 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; + +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.Calendar; +import java.util.GregorianCalendar; + + + +//@@author sham-sheer +/** + * Represents a Person's date of meeting in the address book. + * Guarantees: immutable; is always valid + */ +public class Meet { + public static final String MESSAGE_DATE_CONSTRAINTS = + "Make sure date is in this format: DD/MM/YYYY"; + public static final String DATE_VALIDATION_REGEX = + "^(((0[1-9]|[12]\\d|3[01])\\/(0[13578]|1[02])\\/((19|[2-9]\\d)\\d{2}))|((0[1-9]|[12]\\d|30)\\/" + + "(0[13456789]|1[012])\\/((19|[2-9]\\d)\\d{2}))|((0[1-9]|1\\d|2[0-8])\\/02\\/((19|[2-9]\\d)\\" + + "d{2}))|(29\\/02\\/((1[6-9]|[2-9]\\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579]" + + "[26])00))))$"; + + public final String value; + + public Meet(String meet) { + requireNonNull(meet); + if (meet.isEmpty()) { + this.value = ""; + } else { + checkArgument(isValidDate(meet), MESSAGE_DATE_CONSTRAINTS); + this.value = meet; + } + } + /** + * Converts date to seconds + */ + public static long convertDateToSeconds(String date) { + if (date == "") { + return 0; + } + int day = Integer.parseInt(date.toString().substring(0, + 2)); + int month = Integer.parseInt(date.toString().substring(3, + 5)); + int year = Integer.parseInt(date.toString().substring(6, + 10)); + Calendar calendar = new GregorianCalendar(); + calendar.set(year, month - 1, day); + long seconds = calendar.getTimeInMillis(); + return seconds; + } + + /** + * Converts meet date to a time that is relative to current date, for sorting purposes + */ + public static long dateToInt(String date) { + Calendar calendar = Calendar.getInstance(); + long longDate = convertDateToSeconds(date.toString()); + long currentDate = calendar.getTimeInMillis(); + long timeDiff = longDate - currentDate; + if (timeDiff < 0) { + return Long.MAX_VALUE; + } else { + return timeDiff; + } + } + + + public static boolean isValidDate(String test) { + return test.matches(DATE_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Meet // instanceof handles nulls + && this.value.equals(((Meet) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + + } +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index ec9f2aa5e919..6f02251c7fb6 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -1,6 +1,8 @@ package seedu.address.model.person; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.model.person.Birthday.birthDateToInt; +import static seedu.address.model.person.Meet.dateToInt; import java.util.Collections; import java.util.Objects; @@ -17,21 +19,28 @@ public class Person { private final Name name; private final Phone phone; - private final Email email; - private final Address address; - + private final Birthday birthday; + private final LevelOfFriendship levelOfFriendship; + private final UnitNumber unitNumber; + private final Meet meetDate; + private final UniqueCcaList ccas; private final UniqueTagList tags; /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + + public Person(Name name, Phone phone, Birthday birthday, LevelOfFriendship levelOfFriendship, + UnitNumber unitNumber, Set ccas, Meet meetDate, Set tags) { + requireAllNonNull(name, phone, birthday, levelOfFriendship, unitNumber, ccas, tags); this.name = name; this.phone = phone; - this.email = email; - this.address = address; - // protect internal tags from changes in the arg list + this.birthday = birthday; + this.levelOfFriendship = levelOfFriendship; + this.unitNumber = unitNumber; + this.meetDate = meetDate; + // protect internal tags and ccas from changes in the arg list + this.ccas = new UniqueCcaList(ccas); this.tags = new UniqueTagList(tags); } @@ -43,12 +52,26 @@ public Phone getPhone() { return phone; } - public Email getEmail() { - return email; + public Birthday getBirthday() { + return birthday; + } + + public LevelOfFriendship getLevelOfFriendship() { + return levelOfFriendship; } - public Address getAddress() { - return address; + public Meet getMeetDate() { + return meetDate; + } + public UnitNumber getUnitNumber() { + return unitNumber; + } + /** + * Returns an immutable cca set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getCcas() { + return Collections.unmodifiableSet(ccas.toSet()); } /** @@ -58,6 +81,16 @@ public Address getAddress() { public Set getTags() { return Collections.unmodifiableSet(tags.toSet()); } + /** + * Seperate methods for sorting + */ + public int getLevelOfFriendshipInt() { + return Integer.parseInt(levelOfFriendship.value); + } + + public long getMeetDateInt() { return dateToInt(meetDate.toString()); } + + public long getBirthdayInt() { return birthDateToInt(birthday.toString()); } @Override public boolean equals(Object other) { @@ -72,14 +105,15 @@ public boolean equals(Object other) { Person otherPerson = (Person) other; return otherPerson.getName().equals(this.getName()) && otherPerson.getPhone().equals(this.getPhone()) - && otherPerson.getEmail().equals(this.getEmail()) - && otherPerson.getAddress().equals(this.getAddress()); + && otherPerson.getBirthday().equals(this.getBirthday()) + && otherPerson.getLevelOfFriendship().equals(this.getLevelOfFriendship()) + && otherPerson.getUnitNumber().equals(this.getUnitNumber()); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(name, phone, birthday, levelOfFriendship, unitNumber, ccas, tags); } @Override @@ -88,11 +122,15 @@ public String toString() { builder.append(getName()) .append(" Phone: ") .append(getPhone()) - .append(" Email: ") - .append(getEmail()) - .append(" Address: ") - .append(getAddress()) - .append(" Tags: "); + .append(" Birthday: ") + .append(getBirthday()) + .append(" Level Of Friendship: ") + .append(getLevelOfFriendship()) + .append(" Unit Number: ") + .append(getUnitNumber()) + .append(" Ccas: "); + getCcas().forEach(builder::append); + builder.append(" Tags: "); getTags().forEach(builder::append); return builder.toString(); } diff --git a/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicate.java new file mode 100644 index 000000000000..e3da95d5992a --- /dev/null +++ b/src/main/java/seedu/address/model/person/TagContainsKeywordsPredicate.java @@ -0,0 +1,44 @@ +package seedu.address.model.person; + +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.tag.Tag; + +//@@author fuadsahmawi +/** + * Tests that a {@code Person}'s {@code Tags} matches any of the keywords given. + */ + +public class TagContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public TagContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + Iterator ir = person.getTags().iterator(); + StringBuilder tag = new StringBuilder(); + while (ir.hasNext()) { + tag.append(ir.next().tagName); + tag.append(" "); + } + + String tagS = tag.toString(); + + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(tagS, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TagContainsKeywordsPredicate // instanceof handles nulls + && this.keywords.equals(((TagContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/UniqueCcaList.java b/src/main/java/seedu/address/model/person/UniqueCcaList.java new file mode 100644 index 000000000000..d23a26af5789 --- /dev/null +++ b/src/main/java/seedu/address/model/person/UniqueCcaList.java @@ -0,0 +1,143 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.exceptions.DuplicateDataException; +import seedu.address.commons.util.CollectionUtil; + +//@@author deborahlow97 +/** + * A list of ccas that enforces no nulls and uniqueness between its elements. + * + * Supports minimal set of list operations for the app's features. + * + * @see Cca#equals(Object) + */ +public class UniqueCcaList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Constructs empty CcaList. + */ + public UniqueCcaList() {} + + /** + * Creates a UniqueCcaList using given ccas. + * Enforces no nulls. + */ + public UniqueCcaList(Set ccas) { + requireAllNonNull(ccas); + internalList.addAll(ccas); + + assert CollectionUtil.elementsAreUnique(internalList); + } + + /** + * Returns all ccas in this list as a Set. + * This set is mutable and change-insulated against the internal list. + */ + public Set toSet() { + assert CollectionUtil.elementsAreUnique(internalList); + return new HashSet<>(internalList); + } + + /** + * Replaces the Ccas in this list with those in the argument cca list. + */ + public void setCcas(Set ccas) { + requireAllNonNull(ccas); + internalList.setAll(ccas); + assert CollectionUtil.elementsAreUnique(internalList); + } + + /** + * Ensures every tag in the argument list exists in this object. + */ + public void mergeFrom(UniqueCcaList from) { + final Set alreadyInside = this.toSet(); + from.internalList.stream() + .filter(cca -> !alreadyInside.contains(cca)) + .forEach(internalList::add); + + assert CollectionUtil.elementsAreUnique(internalList); + } + + /** + * Returns true if the list contains an equivalent Cca as the given argument. + */ + public boolean contains(Cca toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a Cca to the list. + * + * @throws DuplicateCcaException if the Cca to add is a duplicate of an existing Cca in the list. + */ + public void add(Cca toAdd) throws DuplicateCcaException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateCcaException(); + } + internalList.add(toAdd); + + assert CollectionUtil.elementsAreUnique(internalList); + } + + @Override + public Iterator iterator() { + assert CollectionUtil.elementsAreUnique(internalList); + return internalList.iterator(); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + assert CollectionUtil.elementsAreUnique(internalList); + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public boolean equals(Object other) { + assert CollectionUtil.elementsAreUnique(internalList); + return other == this // short circuit if same object + || (other instanceof UniqueCcaList // instanceof handles nulls + && this.internalList.equals(((UniqueCcaList) other).internalList)); + } + + /** + * Returns true if the element in this list is equal to the elements in {@code other}. + * The elements do not have to be in the same order. + */ + public boolean equalsOrderInsensitive(UniqueCcaList other) { + assert CollectionUtil.elementsAreUnique(internalList); + assert CollectionUtil.elementsAreUnique(other.internalList); + return this == other || new HashSet<>(this.internalList).equals(new HashSet<>(other.internalList)); + } + + @Override + public int hashCode() { + assert CollectionUtil.elementsAreUnique(internalList); + return internalList.hashCode(); + } + + /** + * Signals that an operation would have violated the 'no duplicates' property of the list. + */ + public static class DuplicateCcaException extends DuplicateDataException { + protected DuplicateCcaException() { + super("Operation would result in duplicate ccas"); + } + } + +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index f2c4c4c585e4..a7d031b3c887 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -3,11 +3,13 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import java.util.Comparator; import java.util.Iterator; import java.util.List; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import seedu.address.commons.core.index.Index; import seedu.address.commons.util.CollectionUtil; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; @@ -18,7 +20,7 @@ * Supports a minimal set of list operations. * * @see Person#equals(Object) - * @see CollectionUtil#elementsAreUnique(Collection) + * @see CollectionUtil #elementsAreUnique(Collection) */ public class UniquePersonList implements Iterable { @@ -81,6 +83,7 @@ public boolean remove(Person toRemove) throws PersonNotFoundException { return personFoundAndDeleted; } + public void setPersons(UniquePersonList replacement) { this.internalList.setAll(replacement.internalList); } @@ -94,6 +97,12 @@ public void setPersons(List persons) throws DuplicatePersonException { setPersons(replacement); } + /** + * Sorts the person list from the start. + * + * @throws PersonNotFoundException if no such person could be found in the list. + */ + public void sortPersons(Index index) throws IndexOutOfBoundsException { sortExecution(index, internalList); } /** * Returns the backing list as an unmodifiable {@code ObservableList}. */ @@ -117,4 +126,28 @@ public boolean equals(Object other) { public int hashCode() { return internalList.hashCode(); } + + //@@author sham-sheer + /** + * Sorting method + */ + public void sortExecution(Index index, ObservableList internalList) { + requireNonNull(index); + if (index.getOneBased() > 3) { + throw new IndexOutOfBoundsException(); + } + if (index.getOneBased() == 1) { + Comparator comparator = Comparator.comparingInt(Person::getLevelOfFriendshipInt); + FXCollections.sort(internalList, comparator); + FXCollections.reverse(internalList); + } + if (index.getOneBased() == 2) { + Comparator comparator = Comparator.comparingLong(Person::getMeetDateInt); + FXCollections.sort(internalList, comparator); + } + if (index.getOneBased() == 3) { + Comparator comparator = Comparator.comparingLong(Person::getBirthdayInt); + FXCollections.sort(internalList, comparator); + } + } } diff --git a/src/main/java/seedu/address/model/person/UnitNumber.java b/src/main/java/seedu/address/model/person/UnitNumber.java new file mode 100644 index 000000000000..48527a7dd521 --- /dev/null +++ b/src/main/java/seedu/address/model/person/UnitNumber.java @@ -0,0 +1,60 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a RC4 resident Unit Number in CollegeZone. + * Guarantees: immutable; is valid as declared in {@link #isValidUnitNumber(String)} + */ +public class UnitNumber { + + public static final String MESSAGE_UNIT_NUMBER_CONSTRAINTS = + "Unit Number should contain #, - and alphanumerical values."; + public static final String UNIT_NUMBER_VALIDATION_REGEX = "\\#[0-9]{1,2}\\-[0-9]{2,3}"; + public final String value; + + //@@author deborahlow97 + /** + * * Constructs an {@code UnitNumber}. + * + * @param unitNumber A valid unit number. + */ + public UnitNumber(String unitNumber) { + requireNonNull(unitNumber); + checkArgument(isValidUnitNumber(unitNumber), MESSAGE_UNIT_NUMBER_CONSTRAINTS); + this.value = unitNumber; + } + + /** + * Returns true if a given string is a valid unit number. + */ + public static boolean isValidUnitNumber(String test) { + + return test.matches(UNIT_NUMBER_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + //@@author + public String getFloor() { + String[] floor = value.split("-"); + return floor[0]; + } + + //@@author deborahlow97 + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UnitNumber // instanceof handles nulls + && this.value.equals(((UnitNumber) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/UnitNumberContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/UnitNumberContainsKeywordsPredicate.java new file mode 100644 index 000000000000..38ac7793feb4 --- /dev/null +++ b/src/main/java/seedu/address/model/person/UnitNumberContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.person; + +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.tag.Tag; + +//@@author zuweitrack +/** + * Tests that a {@code Person}'s {@code UnitNumber} matches any of the keywords given. + */ +public class UnitNumberContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public UnitNumberContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + + Iterator ir = person.getTags().iterator(); + StringBuilder tag = new StringBuilder(); + while (ir.hasNext()) { + tag.append(ir.next().tagName); + tag.append(" "); + } + + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)) + | keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(tag.toString(), keyword)); + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UnitNumberContainsKeywordsPredicate // instanceof handles nulls + && this.keywords.equals(((UnitNumberContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/reminder/DateTime.java b/src/main/java/seedu/address/model/reminder/DateTime.java new file mode 100644 index 000000000000..f7f270d3e2b7 --- /dev/null +++ b/src/main/java/seedu/address/model/reminder/DateTime.java @@ -0,0 +1,66 @@ +package seedu.address.model.reminder; + +import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.address.logic.parser.DateTimeParser.nattyDateAndTimeParser; +import static seedu.address.logic.parser.DateTimeParser.properReminderDateTimeFormat; + +import java.time.LocalDateTime; +import java.util.Optional; + +//@author fuadsahmawi +/** + * Represents a Reminder's date and time in the Calendar. + * Guarantees: immutable; is valid as declared in {@link #isValidDateTime(String)} + */ +public class DateTime { + + public static final String MESSAGE_DATE_TIME_CONSTRAINTS = + "DateTime must be a valid date and time"; + public final String dateTime; + + /** + * Constructs a {@code DateTime}. + * + * @param dateTime A valid DateTime number. + */ + public DateTime(String dateTime) { + if (dateTime.equals("")) { + this.dateTime = ""; + } else { + checkArgument(isValidDateTime(dateTime), MESSAGE_DATE_TIME_CONSTRAINTS); + LocalDateTime localDateTime = nattyDateAndTimeParser(dateTime).get(); + this.dateTime = properReminderDateTimeFormat(localDateTime); + } + } + + /** + * Returns true if a given string is a valid person DateTime number. + */ + public static boolean isValidDateTime(String test) { + Optional localEndDateTime = nattyDateAndTimeParser(test); + if (localEndDateTime.isPresent()) { + return true; + } else { + return false; + } + + } + + @Override + public String toString() { + return dateTime; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DateTime // instanceof handles nulls + && this.dateTime.equals(((DateTime) other).dateTime)); // state check + } + + @Override + public int hashCode() { + return dateTime.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/reminder/EndDateTime.java b/src/main/java/seedu/address/model/reminder/EndDateTime.java new file mode 100644 index 000000000000..de01d74cbdff --- /dev/null +++ b/src/main/java/seedu/address/model/reminder/EndDateTime.java @@ -0,0 +1,66 @@ +package seedu.address.model.reminder; + +import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.address.logic.parser.DateTimeParser.nattyDateAndTimeParser; +import static seedu.address.logic.parser.DateTimeParser.properReminderDateTimeFormat; + +import java.time.LocalDateTime; +import java.util.Optional; + +//@@author fuadsahmawi +/** + * Represents a Reminder's end date and time in the Calendar. + * Guarantees: immutable; is valid as declared in {@link #isValidEndDateTime(String)} + */ +public class EndDateTime { + + + public static final String MESSAGE_END_DATE_TIME_CONSTRAINTS = + "EndDateTime must be a valid date and time"; + public final String endDateTime; + + /** + * Constructs a {@code EndDateTime}. + * + * @param endDateTime A valid endDateTime number. + */ + public EndDateTime(String endDateTime) { + if (endDateTime.equals("")) { + this.endDateTime = ""; + } else { + checkArgument(isValidEndDateTime(endDateTime), MESSAGE_END_DATE_TIME_CONSTRAINTS); + LocalDateTime localEndDateTime = nattyDateAndTimeParser(endDateTime).get(); + this.endDateTime = properReminderDateTimeFormat(localEndDateTime); + } + } + + /** + * Returns true if a given string is a valid person endDateTime number. + */ + public static boolean isValidEndDateTime(String test) { + Optional localEndDateTime = nattyDateAndTimeParser(test); + if (localEndDateTime.isPresent()) { + return true; + } else { + return false; + } + + } + + @Override + public String toString() { + return endDateTime; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EndDateTime // instanceof handles nulls + && this.endDateTime.equals(((EndDateTime) other).endDateTime)); // state check + } + + @Override + public int hashCode() { + return endDateTime.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/reminder/Reminder.java b/src/main/java/seedu/address/model/reminder/Reminder.java new file mode 100644 index 000000000000..d9cc587aeafe --- /dev/null +++ b/src/main/java/seedu/address/model/reminder/Reminder.java @@ -0,0 +1,75 @@ +package seedu.address.model.reminder; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Objects; + +//@@author fuadsahmawi +/** + * Represents a Reminder + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Reminder { + + private final ReminderText reminderText; + private final DateTime dateTime; + private final EndDateTime endDateTime; + + /** + * Every field must be present and not null. + */ + + public Reminder(ReminderText reminderText, DateTime dateTime, EndDateTime endDateTime) { + requireAllNonNull(reminderText, dateTime); + this.reminderText = reminderText; + this.dateTime = dateTime; + this.endDateTime = endDateTime; + } + + public ReminderText getReminderText() { + return reminderText; + } + + public DateTime getDateTime() { + return dateTime; + } + + public EndDateTime getEndDateTime() { + return endDateTime; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Reminder)) { + return false; + } + + Reminder otherReminder = (Reminder) other; + return otherReminder.getReminderText().equals(this.getReminderText()) + && otherReminder.getDateTime().equals(this.getDateTime()) + && otherReminder.getEndDateTime().equals(this.getEndDateTime()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(reminderText, dateTime, endDateTime); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(" Reminder: ") + .append(getReminderText()) + .append(" Date & Time: ") + .append(getDateTime()) + .append(" End Date & Time: ") + .append(getEndDateTime()); + + return builder.toString(); + } +} diff --git a/src/main/java/seedu/address/model/reminder/ReminderText.java b/src/main/java/seedu/address/model/reminder/ReminderText.java new file mode 100644 index 000000000000..172908464ab5 --- /dev/null +++ b/src/main/java/seedu/address/model/reminder/ReminderText.java @@ -0,0 +1,61 @@ +package seedu.address.model.reminder; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author fuadsahmawi +/** + * Represents a Reminder's text in the Calendar. + * Guarantees: immutable; is valid as declared in {@link #isValidReminderText(String)} + */ +public class ReminderText { + + public static final String MESSAGE_REMINDER_TEXT_CONSTRAINTS = + "Reminder text can be any expression that are not just whitespaces."; + public static final String REMINDER_TEXT_VALIDATION_REGEX = "^(?!\\s*$).+"; + public final String reminderText; + + /** + * Constructs a {@code ReminderText}. + * + * @param reminderText A valid reminder text. + */ + public ReminderText(String reminderText) { + requireNonNull(reminderText); + checkArgument(isValidGoalText(reminderText), MESSAGE_REMINDER_TEXT_CONSTRAINTS); + this.reminderText = reminderText; + } + + /** + * Returns true if a given string is a valid reminder text. + */ + public static boolean isValidReminderText(String test) { + return test.matches(REMINDER_TEXT_VALIDATION_REGEX); + } + + /** + * Returns true if a given string is a valid reminder text. + */ + public static boolean isValidGoalText(String test) { + return test.matches(REMINDER_TEXT_VALIDATION_REGEX); + } + + @Override + public String toString() { + return reminderText; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.address.model.goal.GoalText // instanceof handles nulls + && this.reminderText.equals((( + seedu.address.model.reminder.ReminderText) other).reminderText)); // state check + } + + @Override + public int hashCode() { + return reminderText.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/reminder/ReminderTextPredicate.java b/src/main/java/seedu/address/model/reminder/ReminderTextPredicate.java new file mode 100644 index 000000000000..66f71d1644f0 --- /dev/null +++ b/src/main/java/seedu/address/model/reminder/ReminderTextPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.reminder; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +//@@author fuadsahmawi +/** + * Tests that a {@code Reminder}'s {@code ReminderText} matches any of the keywords given. + */ +public class ReminderTextPredicate implements Predicate { + private final List keywords; + + public ReminderTextPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Reminder reminder) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(reminder.getReminderText().toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ReminderTextPredicate // instanceof handles nulls + && this.keywords.equals(((ReminderTextPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/reminder/UniqueReminderList.java b/src/main/java/seedu/address/model/reminder/UniqueReminderList.java new file mode 100644 index 000000000000..ca239d06bd7f --- /dev/null +++ b/src/main/java/seedu/address/model/reminder/UniqueReminderList.java @@ -0,0 +1,119 @@ +package seedu.address.model.reminder; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.reminder.exceptions.DuplicateReminderException; +import seedu.address.model.reminder.exceptions.ReminderNotFoundException; + +//@@author fuadsahmawi +/** + * A list of reminders that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Reminder#equals(Object) + */ +public class UniqueReminderList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent reminder as the given argument. + */ + public boolean contains(Reminder toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a reminder to the list. + * + * @throws DuplicateReminderException if the reminder to add is a duplicate of an existing reminder in the list. + */ + public void add(Reminder toAdd) throws DuplicateReminderException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateReminderException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the reminder {@code target} in the list with {@code editedReminder}. + * + * @throws DuplicateReminderException if the replacement is equivalent to another existing reminder in the list. + * @throws ReminderNotFoundException if {@code target} could not be found in the list. + */ + public void setReminder(Reminder target, Reminder editedReminder) + throws DuplicateReminderException, ReminderNotFoundException { + requireNonNull(editedReminder); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new ReminderNotFoundException(); + } + + if (!target.equals(editedReminder) && internalList.contains(editedReminder)) { + throw new DuplicateReminderException(); + } + + internalList.set(index, editedReminder); + } + + /** + * Removes the equivalent reminder from the list. + * + * @throws ReminderNotFoundException if no such reminder could be found in the list. + */ + public boolean remove(Reminder toRemove) throws ReminderNotFoundException { + requireNonNull(toRemove); + final boolean reminderFoundAndDeleted = internalList.remove(toRemove); + if (!reminderFoundAndDeleted) { + throw new ReminderNotFoundException(); + } + return reminderFoundAndDeleted; + } + + public void setReminders(UniqueReminderList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setReminders(List reminders) throws DuplicateReminderException { + requireAllNonNull(reminders); + final UniqueReminderList replacement = new UniqueReminderList(); + for (final Reminder reminder : reminders) { + replacement.add(reminder); + } + setReminders(replacement); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueReminderList // instanceof handles nulls + && this.internalList.equals(((UniqueReminderList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/reminder/exceptions/DuplicateReminderException.java b/src/main/java/seedu/address/model/reminder/exceptions/DuplicateReminderException.java new file mode 100644 index 000000000000..ddec78c25a84 --- /dev/null +++ b/src/main/java/seedu/address/model/reminder/exceptions/DuplicateReminderException.java @@ -0,0 +1,13 @@ +package seedu.address.model.reminder.exceptions; + +import seedu.address.commons.exceptions.DuplicateDataException; + +//@@author fuadsahmawi +/** + * Signals that the operation will result in duplicate Reminder objects. + */ +public class DuplicateReminderException extends DuplicateDataException { + public DuplicateReminderException() { + super("Operation will result in duplicate reminders"); + } +} diff --git a/src/main/java/seedu/address/model/reminder/exceptions/ReminderNotFoundException.java b/src/main/java/seedu/address/model/reminder/exceptions/ReminderNotFoundException.java new file mode 100644 index 000000000000..4a497e4fdf39 --- /dev/null +++ b/src/main/java/seedu/address/model/reminder/exceptions/ReminderNotFoundException.java @@ -0,0 +1,8 @@ +package seedu.address.model.reminder.exceptions; + +//@@author fuadsahmawi +/** + * Signals that the operation is unable to find the specified reminder. + */ +public class ReminderNotFoundException extends Exception { +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index 65bdd769995d..47026f6cdcbb 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -47,6 +47,7 @@ public int hashCode() { /** * Format state as text for viewing. */ + public String toString() { return '[' + tagName + ']'; } diff --git a/src/main/java/seedu/address/model/util/SampleCollegeZone.java b/src/main/java/seedu/address/model/util/SampleCollegeZone.java new file mode 100644 index 000000000000..9e702dca2d7b --- /dev/null +++ b/src/main/java/seedu/address/model/util/SampleCollegeZone.java @@ -0,0 +1,48 @@ +package seedu.address.model.util; + +import static seedu.address.model.util.SampleDataUtil.getSamplePersons; +import static seedu.address.model.util.SampleGoalDataUtil.getSampleGoals; +import static seedu.address.model.util.SampleReminderDataUtil.getSampleReminders; + +import seedu.address.model.AddressBook; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.goal.Goal; +import seedu.address.model.goal.exceptions.DuplicateGoalException; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.reminder.exceptions.DuplicateReminderException; + + +//@@author deborahlow97 +/** + * Contains method to get a sample CollegeZone data + */ +public class SampleCollegeZone { + + public static ReadOnlyAddressBook getSampleCollegeZone() { + AddressBook sampleCz = new AddressBook(); + try { + for (Person samplePerson : getSamplePersons()) { + sampleCz.addPerson(samplePerson); + } + } catch (DuplicatePersonException e) { + throw new AssertionError("sample data cannot contain duplicate persons", e); + } + try { + for (Goal sampleGoal : getSampleGoals()) { + sampleCz.addGoal(sampleGoal); + } + } catch (DuplicateGoalException e) { + throw new AssertionError("sample data cannot contain duplicate goals", e); + } + try { + for (Reminder sampleReminder : getSampleReminders()) { + sampleCz.addReminder(sampleReminder); + } + } catch (DuplicateReminderException e) { + throw new AssertionError("sample data cannot contain duplicate reminders", e); + } + return sampleCz; + } +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index aea96bfb31f3..faa396bfc49d 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -5,38 +5,93 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; +import seedu.address.model.person.Birthday; +import seedu.address.model.person.Cca; +import seedu.address.model.person.LevelOfFriendship; +import seedu.address.model.person.Meet; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.UnitNumber; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.tag.Tag; +//@@author deborahlow97 /** - * Contains utility methods for populating {@code AddressBook} with sample data. + * Contains utility methods for populating {@code CollegeZone} with sample data. */ public class SampleDataUtil { + + public static final Meet MEET_DATE = new Meet("15/04/2018"); + public static Person[] getSamplePersons() { return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Birthday("01/01/1997"), + new LevelOfFriendship("5"), new UnitNumber("#06-40"), getCcaSet("Basketball"), + MEET_DATE, getTagSet("friends", "RA")), + new Person(new Name("Bernice Yu"), new Phone("99272758"), new Birthday("21/02/1990"), + new LevelOfFriendship("9"), new UnitNumber("#07-18"), getCcaSet(), + new Meet("15/05/2018"), getTagSet("colleagues", "friends")), + new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Birthday("05/09/1980"), + new LevelOfFriendship("1"), new UnitNumber("#11-04"), getCcaSet("Swimming"), + new Meet("15/05/2018"), getTagSet("neighbours")), + new Person(new Name("David Li"), new Phone("91031282"), new Birthday("20/02/1995"), + new LevelOfFriendship("6"), new UnitNumber("#16-43"), getCcaSet(), + new Meet("16/04/2018"), getTagSet("family")), + new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Birthday("01/01/1999"), + new LevelOfFriendship("7"), new UnitNumber("#16-41"), getCcaSet(), + new Meet("17/04/2018"), getTagSet("classmates")), + new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Birthday("02/04/1995"), + new LevelOfFriendship("10"), new UnitNumber("#6-43"), getCcaSet("Computing club", "Anime Club"), + new Meet("18/04/2018"), getTagSet("colleagues")), + new Person(new Name("James Mee"), new Phone("98887555"), new Birthday("22/08/1992"), + new LevelOfFriendship("1"), new UnitNumber("#06-40"), getCcaSet("Tennis"), + new Meet("19/04/2018"), getTagSet("RA")), + new Person(new Name("Jane Ray"), new Phone("93336444"), new Birthday("25/09/1991"), + new LevelOfFriendship("1"), new UnitNumber("#07-40"), getCcaSet("Chess Club"), + new Meet("20/04/2018"), getTagSet("RA")), + new Person(new Name("Deborah Low"), new Phone("91162930"), new Birthday("24/05/1997"), + new LevelOfFriendship("9"), new UnitNumber("#10-24"), getCcaSet("Aerobics Club"), + new Meet("21/04/2018"), getTagSet("colleagues")), + new Person(new Name("Royce Lew"), new Phone("93265932"), new Birthday("10/04/1996"), + new LevelOfFriendship("5"), new UnitNumber("#02-021"), getCcaSet(), + new Meet("22/04/2018"), getTagSet("boyfriend")), + new Person(new Name("Kaden Yeo"), new Phone("82350332"), new Birthday("28/03/2001"), + new LevelOfFriendship("6"), new UnitNumber("#6-20"), getCcaSet("shooting"), + new Meet("23/04/2018"), getTagSet("friends")), + new Person(new Name("Matthew Chiang"), new Phone("92624417"), new Birthday("02/04/1995"), + new LevelOfFriendship("4"), new UnitNumber("#20-43"), getCcaSet("Anime Club"), + new Meet("24/04/2018"), getTagSet("classmate")), + new Person(new Name("Loh Sin Yuen"), new Phone("92624417"), new Birthday("02/05/1995"), + new LevelOfFriendship("10"), new UnitNumber("#03-63"), getCcaSet("dance"), + new Meet("25/04/2018"), getTagSet("schoolmate")), + new Person(new Name("Florence Chiang"), new Phone("92624417"), new Birthday("02/06/1995"), + new LevelOfFriendship("10"), new UnitNumber("#6-97"), getCcaSet("volleyball"), + new Meet("26/04/2018"), getTagSet("bff")), + new Person(new Name("Daniel Low"), new Phone("92624417"), new Birthday("12/04/1995"), + new LevelOfFriendship("1"), new UnitNumber("#7-473"), getCcaSet("Muay Thai"), + new Meet("27/04/2018"), getTagSet("cousin")), + new Person(new Name("Rachel Lee Yan Ling"), new Phone("92624417"), new Birthday("23/04/1995"), + new LevelOfFriendship("3"), new UnitNumber("#6-69"), getCcaSet("Computing club", "Anime Club"), + new Meet("28/04/2018"), getTagSet("cousin")), + new Person(new Name("Sarah tan"), new Phone("92624417"), new Birthday("27/04/1999"), + new LevelOfFriendship("2"), new UnitNumber("#8-43"), getCcaSet("Computing club", "Anime Club"), + new Meet("28/04/2018"), getTagSet()), + new Person(new Name("Amanda Soh"), new Phone("92624417"), new Birthday("02/12/1995"), + new LevelOfFriendship("1"), new UnitNumber("#24-579"), getCcaSet("Computing club", "Anime Club"), + new Meet("17/06/2018"), getTagSet("exgirlfriend")), + new Person(new Name("Marlene Koh"), new Phone("92624417"), new Birthday("02/07/1997"), + new LevelOfFriendship("10"), new UnitNumber("#02-222"), getCcaSet("Pool"), + new Meet("17/07/2018"), getTagSet("closefriend")), + new Person(new Name("Johnny Depp"), new Phone("92624417"), new Birthday("02/12/1994"), + new LevelOfFriendship("2"), new UnitNumber("#01-346"), getCcaSet("Pool"), + new Meet("17/08/2018"), getTagSet("malafriend")), + new Person(new Name("Aditya"), new Phone("92624417"), new Birthday("02/04/1998"), + new LevelOfFriendship("3"), new UnitNumber("#6-43"), getCcaSet(), + new Meet("17/09/2018"), getTagSet("malafriend")), + new Person(new Name("Fuad"), new Phone("92624417"), new Birthday("20/04/1995"), + new LevelOfFriendship("9"), new UnitNumber("#6-43"), getCcaSet("Floorball"), + new Meet("17/04/2018"), getTagSet("colleagues")) }; } @@ -52,6 +107,18 @@ public static ReadOnlyAddressBook getSampleAddressBook() { } } + /** + * Returns a cca set containing the list of strings given. + */ + public static Set getCcaSet(String... strings) { + HashSet ccas = new HashSet<>(); + for (String s : strings) { + ccas.add(new Cca(s)); + } + + return ccas; + } + /** * Returns a tag set containing the list of strings given. */ diff --git a/src/main/java/seedu/address/model/util/SampleGoalDataUtil.java b/src/main/java/seedu/address/model/util/SampleGoalDataUtil.java new file mode 100644 index 000000000000..7f8993458617 --- /dev/null +++ b/src/main/java/seedu/address/model/util/SampleGoalDataUtil.java @@ -0,0 +1,100 @@ +package seedu.address.model.util; + +import static seedu.address.logic.parser.DateTimeParser.getLocalDateTimeFromString; + +import seedu.address.model.AddressBook; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.goal.Completion; +import seedu.address.model.goal.EndDateTime; +import seedu.address.model.goal.Goal; +import seedu.address.model.goal.GoalText; +import seedu.address.model.goal.Importance; +import seedu.address.model.goal.StartDateTime; +import seedu.address.model.goal.exceptions.DuplicateGoalException; + +//@@author deborahlow97 +/** + * Contains utility methods for populating {@code CollegeZone} with sample data. + */ +public class SampleGoalDataUtil { + + public static final EndDateTime EMPTY_END_DATE_TIME = new EndDateTime(""); + + public static Goal[] getSampleGoals() { + return new Goal[] { + + new Goal(new Importance("1"), new GoalText("finish cs2103"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("2"), new GoalText("no"), + new StartDateTime(getLocalDateTimeFromString("2018-04-08 12:12")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("3"), new GoalText("grow taller"), + new StartDateTime(getLocalDateTimeFromString("1997-04-08 12:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("3"), new GoalText("finish cs2105 assignments"), + new StartDateTime(getLocalDateTimeFromString("2018-04-08 10:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("1"), new GoalText("learning digital art"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:39")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("2"), new GoalText("finish cs2103!!!!"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("10"), new GoalText("finish cs2103!!!!"), + new StartDateTime(getLocalDateTimeFromString("2018-03-18 08:30")), + new EndDateTime("03/04/2018 12:30"), new Completion(true)), + new Goal(new Importance("6"), new GoalText("lose 0.5kg by this week"), + new StartDateTime(getLocalDateTimeFromString("2018-04-06 19:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("10"), new GoalText("Find love <3"), + new StartDateTime(getLocalDateTimeFromString("2014-04-08 20:30")), + new EndDateTime("02/02/2018 12:30"), new Completion(true)), + new Goal(new Importance("7"), new GoalText("water plants"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("5"), new GoalText("buy dog food"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("4"), new GoalText("Take the stairs more often!"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + new EndDateTime("04/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("10"), new GoalText("Eat PGP MALA once every week"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + new EndDateTime("07/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("7"), new GoalText("Make more friends in uni"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:45")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("9"), new GoalText("Go CCA regularly"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 13:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("9"), new GoalText("Drink 8 cups of water everyday"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 01:59")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("8"), new GoalText("Get A for CS2105"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 02:30")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("8"), new GoalText("Get A- for GEH1036"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 03:30")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + new Goal(new Importance("10"), new GoalText("Aim to increase CAP by 0.2 by the end of this semester"), + new StartDateTime(getLocalDateTimeFromString("2017-02-18 12:30")), + EMPTY_END_DATE_TIME, new Completion(false)), + new Goal(new Importance("4"), new GoalText("Do 50 squats EVERYDAY"), + new StartDateTime(getLocalDateTimeFromString("2017-04-08 12:30")), + new EndDateTime("03/06/2018 12:30"), new Completion(true)), + }; + } + + public static ReadOnlyAddressBook getSampleGoalAddressBook() { + try { + AddressBook sampleAb = new AddressBook(); + for (Goal sampleGoal : getSampleGoals()) { + sampleAb.addGoal(sampleGoal); + } + return sampleAb; + } catch (DuplicateGoalException e) { + throw new AssertionError("sample data cannot contain duplicate goals", e); + } + } +} diff --git a/src/main/java/seedu/address/model/util/SampleReminderDataUtil.java b/src/main/java/seedu/address/model/util/SampleReminderDataUtil.java new file mode 100644 index 000000000000..928057d8e047 --- /dev/null +++ b/src/main/java/seedu/address/model/util/SampleReminderDataUtil.java @@ -0,0 +1,49 @@ +package seedu.address.model.util; + +import seedu.address.model.reminder.DateTime; +import seedu.address.model.reminder.EndDateTime; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.reminder.ReminderText; + +//@@author fuadsahmawi +/** + * Contains utility methods for populating {@code CollegeZone} with sample reminder data. + */ +public class SampleReminderDataUtil { + + public static Reminder[] getSampleReminders() { + return new Reminder[] { + + new Reminder(new ReminderText("CS2103T Submission"), + new DateTime("2018-04-15 23:00"), + new EndDateTime("2018-04-15 23:59")), + new Reminder(new ReminderText("Gym Session"), + new DateTime("2018-04-13 14:00"), + new EndDateTime("2018-04-13 16:00")), + new Reminder(new ReminderText("Gym Session"), + new DateTime("2018-04-06 14:00"), + new EndDateTime("2018-04-06 16:00")), + new Reminder(new ReminderText("Gym Session"), + new DateTime("2018-04-20 14:00"), + new EndDateTime("2018-04-20 16:00")), + new Reminder(new ReminderText("Gym Session"), + new DateTime("2018-04-27 14:00"), + new EndDateTime("2018-04-27 16:00")), + new Reminder(new ReminderText("Recess Week"), + new DateTime("2018-04-23 00:00"), + new EndDateTime("2018-04-27 23:59")), + new Reminder(new ReminderText("CS2103T Software Demo"), + new DateTime("2018-04-19 09:00"), + new EndDateTime("2018-04-19 10:00")), + new Reminder(new ReminderText("CS2103T Group Meeting"), + new DateTime("2018-04-14 11:00"), + new EndDateTime("2018-04-14 18:00")), + new Reminder(new ReminderText("Chalet"), + new DateTime("2018-04-21 10:00"), + new EndDateTime("2018-04-22 20:00")), + new Reminder(new ReminderText("Medical Appointment"), + new DateTime("2018-04-05 15:00"), + new EndDateTime("2018-04-05 17:00")), + }; + } +} diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java index cf5b527c063a..7e5f11103a6a 100644 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ b/src/main/java/seedu/address/storage/AddressBookStorage.java @@ -41,4 +41,11 @@ public interface AddressBookStorage { */ void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) throws IOException; + /** + * Saves the given {@link ReadOnlyAddressBook} to a temporary local backup file. + * @param addressBook cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void backupAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 53967b391a5a..e1eb646eda9e 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -77,6 +77,12 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) th addressBookStorage.saveAddressBook(addressBook, filePath); } + @Override + public void backupAddressBook(ReadOnlyAddressBook addressBook) throws IOException { + logger.fine("Attempting to write to backup local data file"); + addressBookStorage.backupAddressBook(addressBook); + } + @Override @Subscribe diff --git a/src/main/java/seedu/address/storage/XmlAdaptedCca.java b/src/main/java/seedu/address/storage/XmlAdaptedCca.java new file mode 100644 index 000000000000..07efcadb0c9e --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedCca.java @@ -0,0 +1,63 @@ +package seedu.address.storage; + +import javax.xml.bind.annotation.XmlValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Cca; + +//@@author deborahlow97 +/** + * JAXB-friendly adapted version of the Cca. + */ +public class XmlAdaptedCca { + + @XmlValue + private String ccaName; + + /** + * Constructs an XmlAdaptedCca. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedCca() {} + + /** + * Constructs a {@code XmlAdaptedCca} with the given {@code ccaName}. + */ + public XmlAdaptedCca(String ccaName) { + this.ccaName = ccaName; + } + + /** + * Converts a given Tag into this class for JAXB use. + * + * @param source future changes to this will not affect the created + */ + public XmlAdaptedCca(Cca source) { + ccaName = source.ccaName; + } + + /** + * Converts this jaxb-friendly adapted cca object into the model's Cca object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person + */ + public Cca toModelType() throws IllegalValueException { + if (!Cca.isValidCcaName(ccaName)) { + throw new IllegalValueException(Cca.MESSAGE_CCA_CONSTRAINTS); + } + return new Cca(ccaName); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedCca)) { + return false; + } + + return ccaName.equals(((XmlAdaptedCca) other).ccaName); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedGoal.java b/src/main/java/seedu/address/storage/XmlAdaptedGoal.java new file mode 100644 index 000000000000..f109290e9855 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedGoal.java @@ -0,0 +1,131 @@ +package seedu.address.storage; + +import java.util.Objects; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.goal.Completion; +import seedu.address.model.goal.EndDateTime; +import seedu.address.model.goal.Goal; +import seedu.address.model.goal.GoalText; +import seedu.address.model.goal.Importance; +import seedu.address.model.goal.StartDateTime; + +//@@author deborahlow97 +/** + * JAXB-friendly version of the Goal. + */ +public class XmlAdaptedGoal { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Goal's %s field is missing!"; + + @XmlElement(required = true) + private String importance; + @XmlElement(required = true) + private String goalText; + @XmlElement(required = true) + private String startDateTime; + @XmlElement(required = true) + private String endDateTime; + @XmlElement(required = true) + private String completion; + + /** + * Constructs an XmlAdaptedGoal. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedGoal() {} + + /** + * Constructs an {@code XmlAdaptedGoal} with the given goal details. + */ + public XmlAdaptedGoal(String importance, String goalText, String startDateTime, String endDateTime, + String completion) { + this.importance = importance; + this.goalText = goalText; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.completion = completion; + } + + /** + * Converts a given Goal into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedGoal + */ + public XmlAdaptedGoal(Goal source) { + importance = source.getImportance().value; + goalText = source.getGoalText().value; + startDateTime = source.getStartDateTime().value; + endDateTime = source.getEndDateTime().value; + completion = source.getCompletion().value; + } + + /** + * Converts this jaxb-friendly adapted goal object into the model's Goal object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted goal + */ + public Goal toModelType() throws IllegalValueException { + + if (this.importance == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Importance.class.getSimpleName())); + } + if (!Importance.isValidImportance(this.importance)) { + throw new IllegalValueException(Importance.MESSAGE_IMPORTANCE_CONSTRAINTS); + } + final Importance importance = new Importance(this.importance); + + if (this.goalText == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + GoalText.class.getSimpleName())); + } + if (!GoalText.isValidGoalText(this.goalText)) { + throw new IllegalValueException(GoalText.MESSAGE_GOAL_TEXT_CONSTRAINTS); + } + final GoalText goalText = new GoalText(this.goalText); + + if (this.startDateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + StartDateTime.class.getSimpleName())); + } + + final StartDateTime startDateTime = new StartDateTime(this.startDateTime); + + if (this.endDateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, EndDateTime + .class.getSimpleName())); + } + + final EndDateTime endDateTime = new EndDateTime(this.endDateTime); + + if (this.completion == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Completion.class.getSimpleName())); + } + + final Completion completion = new Completion((this.completion.equals("true"))); + return new Goal(importance, goalText, startDateTime, endDateTime, completion); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedGoal)) { + return false; + } + + XmlAdaptedGoal otherGoal = (XmlAdaptedGoal) other; + return Objects.equals(importance, otherGoal.importance) + && Objects.equals(goalText, otherGoal.goalText) + && Objects.equals(startDateTime, otherGoal.startDateTime) + && Objects.equals(endDateTime, otherGoal.endDateTime) + && Objects.equals(completion, otherGoal.completion); + } +} + diff --git a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java index 2cd92dc4fd20..052ffe6c76c5 100644 --- a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java @@ -9,11 +9,14 @@ import javax.xml.bind.annotation.XmlElement; import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; +import seedu.address.model.person.Birthday; +import seedu.address.model.person.Cca; +import seedu.address.model.person.LevelOfFriendship; +import seedu.address.model.person.Meet; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.UnitNumber; import seedu.address.model.tag.Tag; /** @@ -28,10 +31,16 @@ public class XmlAdaptedPerson { @XmlElement(required = true) private String phone; @XmlElement(required = true) - private String email; + private String birthday; @XmlElement(required = true) - private String address; + private String levelOfFriendship; + @XmlElement(required = true) + private String unitNumber; + @XmlElement(required = true) + private String meetDate; + @XmlElement + private List ccas = new ArrayList<>(); @XmlElement private List tagged = new ArrayList<>(); @@ -44,11 +53,17 @@ public XmlAdaptedPerson() {} /** * Constructs an {@code XmlAdaptedPerson} with the given person details. */ - public XmlAdaptedPerson(String name, String phone, String email, String address, List tagged) { + public XmlAdaptedPerson(String name, String phone, String birthday, String levelOfFriendship, String unitNumber, + String meetDate, List ccas, List tagged) { this.name = name; this.phone = phone; - this.email = email; - this.address = address; + this.birthday = birthday; + this.levelOfFriendship = levelOfFriendship; + this.unitNumber = unitNumber; + this.meetDate = meetDate; + if (ccas != null) { + this.ccas = new ArrayList<>(ccas); + } if (tagged != null) { this.tagged = new ArrayList<>(tagged); } @@ -62,9 +77,16 @@ public XmlAdaptedPerson(String name, String phone, String email, String address, public XmlAdaptedPerson(Person source) { name = source.getName().fullName; phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; + birthday = source.getBirthday().value; + levelOfFriendship = source.getLevelOfFriendship().value; + unitNumber = source.getUnitNumber().value; + meetDate = source.getMeetDate().value; + ccas = new ArrayList<>(); + for (Cca cca : source.getCcas()) { + ccas.add(new XmlAdaptedCca(cca)); + } tagged = new ArrayList<>(); + for (Tag tag : source.getTags()) { tagged.add(new XmlAdaptedTag(tag)); } @@ -76,6 +98,12 @@ public XmlAdaptedPerson(Person source) { * @throws IllegalValueException if there were any data constraints violated in the adapted person */ public Person toModelType() throws IllegalValueException { + //@@author deborahlow97 + final List personCcas = new ArrayList<>(); + for (XmlAdaptedCca cca : ccas) { + personCcas.add(cca.toModelType()); + } + //@@author final List personTags = new ArrayList<>(); for (XmlAdaptedTag tag : tagged) { personTags.add(tag.toModelType()); @@ -97,24 +125,42 @@ public Person toModelType() throws IllegalValueException { } final Phone phone = new Phone(this.phone); - if (this.email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + //@@author deborahlow97 + if (this.birthday == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Birthday.class.getSimpleName())); + } + if (!Birthday.isValidBirthday(this.birthday)) { + throw new IllegalValueException(Birthday.MESSAGE_BIRTHDAY_CONSTRAINTS); } - if (!Email.isValidEmail(this.email)) { - throw new IllegalValueException(Email.MESSAGE_EMAIL_CONSTRAINTS); + final Birthday birthday = new Birthday(this.birthday); + + if (this.levelOfFriendship == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, LevelOfFriendship + .class.getSimpleName())); } - final Email email = new Email(this.email); + if (!LevelOfFriendship.isValidLevelOfFriendship(this.levelOfFriendship)) { + throw new IllegalValueException(LevelOfFriendship.MESSAGE_LEVEL_OF_FRIENDSHIP_CONSTRAINTS); + } + final LevelOfFriendship levelOfFriendship = new LevelOfFriendship(this.levelOfFriendship); + + //@@author sham-sheer + final Meet meetDate = new Meet(this.meetDate); - if (this.address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + //@@author deborahlow97 + if (this.unitNumber == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + UnitNumber.class.getSimpleName())); } - if (!Address.isValidAddress(this.address)) { - throw new IllegalValueException(Address.MESSAGE_ADDRESS_CONSTRAINTS); + if (!UnitNumber.isValidUnitNumber(this.unitNumber)) { + throw new IllegalValueException(UnitNumber.MESSAGE_UNIT_NUMBER_CONSTRAINTS); } - final Address address = new Address(this.address); + final UnitNumber unitNumber = new UnitNumber(this.unitNumber); + final Set ccas = new HashSet<>(personCcas); + //@@author final Set tags = new HashSet<>(personTags); - return new Person(name, phone, email, address, tags); + return new Person(name, phone, birthday, levelOfFriendship, unitNumber, ccas, meetDate, tags); } @Override @@ -130,8 +176,11 @@ public boolean equals(Object other) { XmlAdaptedPerson otherPerson = (XmlAdaptedPerson) other; return Objects.equals(name, otherPerson.name) && Objects.equals(phone, otherPerson.phone) - && Objects.equals(email, otherPerson.email) - && Objects.equals(address, otherPerson.address) + && Objects.equals(birthday, otherPerson.birthday) + && Objects.equals(levelOfFriendship, otherPerson.levelOfFriendship) + && Objects.equals(unitNumber, otherPerson.unitNumber) + //&& Objects.equals(meetDate, otherPerson.meetDate) + && ccas.equals(otherPerson.ccas) && tagged.equals(otherPerson.tagged); } } diff --git a/src/main/java/seedu/address/storage/XmlAdaptedReminder.java b/src/main/java/seedu/address/storage/XmlAdaptedReminder.java new file mode 100644 index 000000000000..1627248aa2a0 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedReminder.java @@ -0,0 +1,105 @@ +package seedu.address.storage; + +import java.util.Objects; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.reminder.DateTime; +import seedu.address.model.reminder.EndDateTime; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.reminder.ReminderText; + +//@@author fuadsahmawi +/** + * JAXB-friendly version of the Reminder. + */ +public class XmlAdaptedReminder { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Reminder's %s field is missing!"; + + @XmlElement(required = true) + private String reminderText; + @XmlElement(required = true) + private String dateTime; + @XmlElement(required = true) + private String endDateTime; + + /** + * Constructs an XmlAdaptedReminder. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedReminder() {} + + /** + * Constructs an {@code XmlAdaptedReminder} with the given person details. + */ + public XmlAdaptedReminder(String reminderText, String dateTime, String endDateTime) { + this.reminderText = reminderText; + this.dateTime = dateTime; + this.endDateTime = endDateTime; + } + + /** + * Converts a given Reminder into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedReminder + */ + public XmlAdaptedReminder(Reminder source) { + reminderText = source.getReminderText().toString(); + dateTime = source.getDateTime().toString(); + endDateTime = source.getEndDateTime().toString(); + } + + /** + * Converts this jaxb-friendly adapted reminder object into the model's Reminder object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted reminder + */ + public Reminder toModelType() throws IllegalValueException { + if (this.reminderText == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + ReminderText.class.getSimpleName())); + } + if (!ReminderText.isValidReminderText(this.reminderText)) { + throw new IllegalValueException(ReminderText.MESSAGE_REMINDER_TEXT_CONSTRAINTS); + } + final ReminderText reminderText = new ReminderText(this.reminderText); + + if (this.dateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + DateTime.class.getSimpleName())); + } + if (!DateTime.isValidDateTime(this.dateTime)) { + throw new IllegalValueException(DateTime.MESSAGE_DATE_TIME_CONSTRAINTS); + } + final DateTime dateTime = new DateTime(this.dateTime); + + if (this.endDateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + EndDateTime.class.getSimpleName())); + } + if (!DateTime.isValidDateTime(this.endDateTime)) { + throw new IllegalValueException(EndDateTime.MESSAGE_END_DATE_TIME_CONSTRAINTS); + } + final EndDateTime endDateTime = new EndDateTime(this.endDateTime); + + return new Reminder(reminderText, dateTime, endDateTime); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedReminder)) { + return false; + } + + XmlAdaptedReminder otherPerson = (XmlAdaptedReminder) other; + return Objects.equals(reminderText, otherPerson.reminderText) + && Objects.equals(dateTime, otherPerson.dateTime) + && Objects.equals(endDateTime, otherPerson.endDateTime); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java b/src/main/java/seedu/address/storage/XmlAddressBookStorage.java index c77ebe67435c..d4a5d1b1fa61 100644 --- a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/XmlAddressBookStorage.java @@ -79,4 +79,14 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) th XmlFileStorage.saveDataToFile(file, new XmlSerializableAddressBook(addressBook)); } + /** + * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)} + * @param addressBook addressBook. Cannot be null + */ + @Override + public void backupAddressBook(ReadOnlyAddressBook addressBook) throws IOException { + requireNonNull(addressBook); + saveAddressBook(addressBook, filePath + ".backup"); + } + } diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java index dc820896c312..d7f312be0a56 100644 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java @@ -20,7 +20,13 @@ public class XmlSerializableAddressBook { @XmlElement private List persons; @XmlElement + private List goals; + @XmlElement + private List ccas; + @XmlElement private List tags; + @XmlElement + private List reminders; /** * Creates an empty XmlSerializableAddressBook. @@ -28,7 +34,10 @@ public class XmlSerializableAddressBook { */ public XmlSerializableAddressBook() { persons = new ArrayList<>(); + goals = new ArrayList<>(); + ccas = new ArrayList<>(); tags = new ArrayList<>(); + reminders = new ArrayList<>(); } /** @@ -37,23 +46,35 @@ public XmlSerializableAddressBook() { public XmlSerializableAddressBook(ReadOnlyAddressBook src) { this(); persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); + goals.addAll(src.getGoalList().stream().map(XmlAdaptedGoal::new).collect(Collectors.toList())); + ccas.addAll(src.getCcaList().stream().map(XmlAdaptedCca::new).collect(Collectors.toList())); tags.addAll(src.getTagList().stream().map(XmlAdaptedTag::new).collect(Collectors.toList())); + reminders.addAll(src.getReminderList().stream().map(XmlAdaptedReminder::new).collect(Collectors.toList())); } /** * Converts this addressbook into the model's {@code AddressBook} object. * * @throws IllegalValueException if there were any data constraints violated or duplicates in the - * {@code XmlAdaptedPerson} or {@code XmlAdaptedTag}. + * {@code XmlAdaptedPerson}or {@code XmlAdaptedCca} or {@code XmlAdaptedTag} or (@code XmlAdaptedReminder) */ public AddressBook toModelType() throws IllegalValueException { AddressBook addressBook = new AddressBook(); + for (XmlAdaptedCca c : ccas) { + addressBook.addCca(c.toModelType()); + } + for (XmlAdaptedGoal g : goals) { + addressBook.addGoal(g.toModelType()); + } for (XmlAdaptedTag t : tags) { addressBook.addTag(t.toModelType()); } for (XmlAdaptedPerson p : persons) { addressBook.addPerson(p.toModelType()); } + for (XmlAdaptedReminder r : reminders) { + addressBook.addReminder(r.toModelType()); + } return addressBook; } @@ -68,6 +89,8 @@ public boolean equals(Object other) { } XmlSerializableAddressBook otherAb = (XmlSerializableAddressBook) other; - return persons.equals(otherAb.persons) && tags.equals(otherAb.tags); + + return persons.equals(otherAb.persons) && ccas.equals(otherAb.ccas) + && tags.equals(otherAb.tags) && reminders.equals(otherAb.reminders); } } diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/address/ui/BrowserPanel.java deleted file mode 100644 index bb0d61380d5a..000000000000 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ /dev/null @@ -1,72 +0,0 @@ -package seedu.address.ui; - -import java.net.URL; -import java.util.logging.Logger; - -import com.google.common.eventbus.Subscribe; - -import javafx.application.Platform; -import javafx.event.Event; -import javafx.fxml.FXML; -import javafx.scene.layout.Region; -import javafx.scene.web.WebView; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; - -/** - * The Browser Panel of the App. - */ -public class BrowserPanel extends UiPart { - - public static final String DEFAULT_PAGE = "default.html"; - public static final String SEARCH_PAGE_URL = - "https://se-edu.github.io/addressbook-level4/DummySearchPage.html?name="; - - private static final String FXML = "BrowserPanel.fxml"; - - private final Logger logger = LogsCenter.getLogger(this.getClass()); - - @FXML - private WebView browser; - - public BrowserPanel() { - super(FXML); - - // To prevent triggering events for typing inside the loaded Web page. - getRoot().setOnKeyPressed(Event::consume); - - loadDefaultPage(); - registerAsAnEventHandler(this); - } - - private void loadPersonPage(Person person) { - loadPage(SEARCH_PAGE_URL + person.getName().fullName); - } - - public void loadPage(String url) { - Platform.runLater(() -> browser.getEngine().load(url)); - } - - /** - * Loads a default HTML file with a background that matches the general theme. - */ - private void loadDefaultPage() { - URL defaultPage = MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE); - loadPage(defaultPage.toExternalForm()); - } - - /** - * Frees resources allocated to the browser. - */ - public void freeResources() { - browser = null; - } - - @Subscribe - private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - loadPersonPage(event.getNewSelection().person); - } -} diff --git a/src/main/java/seedu/address/ui/CalendarPanel.java b/src/main/java/seedu/address/ui/CalendarPanel.java new file mode 100644 index 000000000000..4a1e96c16730 --- /dev/null +++ b/src/main/java/seedu/address/ui/CalendarPanel.java @@ -0,0 +1,128 @@ +package seedu.address.ui; + +import static seedu.address.logic.parser.DateTimeParser.nattyDateAndTimeParser; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +import com.calendarfx.model.Calendar; + +import com.calendarfx.model.CalendarSource; +import com.calendarfx.model.Entry; +import com.calendarfx.model.Interval; +import com.calendarfx.view.CalendarView; +import com.google.common.eventbus.Subscribe; + +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.scene.layout.Region; +import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; + + + + +//@@author fuadsahmawi +/** + * The Calendar Panel of the App. + */ +public class CalendarPanel extends UiPart { + private static final String FXML = "CalendarPanel.fxml"; + + private CalendarView calendarView; + + private ObservableList reminderList; + + private ObservableList personList; + + public CalendarPanel(ObservableList reminderList, ObservableList personList) { + super(FXML); + + this.reminderList = reminderList; + this.personList = personList; + + calendarView = new CalendarView(); + setupCalendar(); + updateCalendar(); + registerAsAnEventHandler(this); + } + + @Subscribe + private void handleNewCalendarEvent(AddressBookChangedEvent event) { + reminderList = event.data.getReminderList(); + personList = event.data.getPersonList(); + Platform.runLater(this::updateCalendar); + } + + /** + * Updates the Calendar with Reminders that are already added + */ + private void updateCalendar() { + setDateAndTime(); + CalendarSource myCalendarSource = new CalendarSource("Reminders and Meetups"); + Calendar calendarRDue = new Calendar("Reminders Already Due"); + Calendar calendarRNotDue = new Calendar("Reminders Not Due"); + Calendar calendarM = new Calendar("Meetups"); + calendarRDue.setStyle(Calendar.Style.getStyle(4)); + calendarRDue.setLookAheadDuration(Duration.ofDays(365)); + calendarRNotDue.setStyle(Calendar.Style.getStyle(1)); + calendarRNotDue.setLookAheadDuration(Duration.ofDays(365)); + calendarM.setStyle(Calendar.Style.getStyle(3)); + myCalendarSource.getCalendars().add(calendarRDue); + myCalendarSource.getCalendars().add(calendarRNotDue); + myCalendarSource.getCalendars().add(calendarM); + for (Reminder reminder : reminderList) { + LocalDateTime ldtstart = nattyDateAndTimeParser(reminder.getDateTime().toString()).get(); + LocalDateTime ldtend = nattyDateAndTimeParser(reminder.getEndDateTime().toString()).get(); + LocalDateTime now = LocalDateTime.now(); + if (now.isBefore(ldtend)) { + calendarRNotDue.addEntry(new Entry( + reminder.getReminderText().toString(), new Interval(ldtstart, ldtend))); + } else { + calendarRDue.addEntry(new Entry(reminder.getReminderText().toString(), new Interval(ldtstart, ldtend))); + } + } + //@@author sham-sheer + for (Person person : personList) { + String meetDate = person.getMeetDate().toString(); + if (!meetDate.isEmpty()) { + int day = Integer.parseInt(meetDate.substring(0, + 2)); + int month = Integer.parseInt(meetDate.substring(3, + 5)); + int year = Integer.parseInt(meetDate.substring(6, + 10)); + calendarM.addEntry(new Entry("Meeting " + person.getName().toString(), + new Interval(LocalDate.of(year, month, day), LocalTime.of(12, 0), + LocalDate.of(year, month, day), LocalTime.of(13, 0)))); + } + } + calendarView.getCalendarSources().add(myCalendarSource); + } + + //@@author fuadsahmawi + private void setDateAndTime() { + calendarView.setToday(LocalDate.now()); + calendarView.setTime(LocalTime.now()); + calendarView.getCalendarSources().clear(); + } + + private void setupCalendar() { + calendarView.setRequestedTime(LocalTime.now()); + calendarView.setToday(LocalDate.now()); + calendarView.setTime(LocalTime.now()); + calendarView.setShowAddCalendarButton(false); + calendarView.setShowSearchField(false); + calendarView.setShowSearchResultsTray(false); + calendarView.setShowPrintButton(false); + calendarView.showMonthPage(); + } + + public CalendarView getRoot() { + return this.calendarView; + } + +} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 9cef588df3c3..dea6b4666090 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -1,6 +1,9 @@ package seedu.address.ui; +import java.util.ArrayList; +import java.util.List; import java.util.logging.Logger; +import java.util.stream.Collectors; import javafx.collections.ObservableList; import javafx.fxml.FXML; @@ -9,6 +12,7 @@ import javafx.scene.layout.Region; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.ui.NewResultAvailableEvent; +import seedu.address.logic.CommandFormatListUtil; import seedu.address.logic.ListElementPointer; import seedu.address.logic.Logic; import seedu.address.logic.commands.CommandResult; @@ -51,6 +55,10 @@ private void handleKeyPress(KeyEvent keyEvent) { navigateToPreviousInput(); break; + case TAB: + keyEvent.consume(); + autocompleteCommand(commandTextField.getText()); + break; case DOWN: keyEvent.consume(); navigateToNextInput(); @@ -147,5 +155,24 @@ private void setStyleToIndicateCommandFailure() { styleClass.add(ERROR_STYLE_CLASS); } + //@@author sham-sheer + /** + * Sets the commandbox to completed command format if the entered substring of the command is valid + * @param text is the command which is to be autocompleted + */ + private void autocompleteCommand(String text) { + ArrayList commandFormatList = CommandFormatListUtil.getCommandFormatList(); + + //retrieve the list of words which begin with text + List autocompleteCommandList = commandFormatList.stream() + .filter(s -> s.startsWith(text)) + .collect(Collectors.toList()); + + //replace input in text field with matched keyword + if (!autocompleteCommandList.isEmpty()) { + replaceText(autocompleteCommandList.get(0)); + } + + } } diff --git a/src/main/java/seedu/address/ui/GoalCard.java b/src/main/java/seedu/address/ui/GoalCard.java new file mode 100644 index 000000000000..fc18e7ced7ee --- /dev/null +++ b/src/main/java/seedu/address/ui/GoalCard.java @@ -0,0 +1,123 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.commons.util.AppUtil; +import seedu.address.model.goal.Goal; + +//@@author deborahlow97 +/** + * An UI component that displays information of a {@code Goal}. + */ +public class GoalCard extends UiPart { + private static final int NOT_COMPLETED_COLOUR_STYLE = 0; + private static final int COMPLETED_COLOUR_STYLE = 1; + private static final String FXML = "GoalListCard.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. + */ + + public final Goal goal; + + @FXML + private HBox goalCardPane; + @FXML + private Label goalText; + @FXML + private Label id; + @FXML + private FlowPane importance; + @FXML + private Label startDateTime; + @FXML + private Label endDateTime; + @FXML + private FlowPane completion; + + + public GoalCard(Goal goal, int displayedIndex) { + super(FXML); + this.goal = goal; + id.setText(displayedIndex + ". "); + goalText.setText(goal.getGoalText().value); + initImportance(goal); + startDateTime.setText("Start " + goal.getStartDateTime().value); + if (goal.getEndDateTime().value.equals("")) { + endDateTime.setText(goal.getEndDateTime().value); + } else { + endDateTime.setText("End " + goal.getEndDateTime().value); + } + initCompletion(goal); + } + + /** + * Creates the completion label for {@code goal}. + */ + private void initCompletion(Goal goal) { + String trueOrFalseString = goal.getCompletion().value; + if (trueOrFalseString.equals("true")) { + Image completedImage = AppUtil.getImage("/images/completedImage.png"); + ImageView completedImageView = new ImageView(completedImage); + completedImageView.setFitHeight(30); + completedImageView.setFitWidth(30); + Label completionLabel = new Label("Completed", completedImageView); + completion.getChildren().add(completionLabel); + } else { + Image ongoingImage = AppUtil.getImage("/images/ongoingImage.png"); + ImageView ongoingImageView = new ImageView(ongoingImage); + ongoingImageView.setFitHeight(27); + ongoingImageView.setFitWidth(27); + Label completionLabel = new Label("Ongoing", ongoingImageView); + completion.getChildren().add(completionLabel); + } + } + + /** + * Creates the importance label for {@code goal}. + */ + private void initImportance(Goal goal) { + String starValue = changeImportanceToStar(goal.getImportance().value); + Label importanceLabel = new Label(starValue); + importanceLabel.getStyleClass().add("yellow"); + importance.getChildren().add(importanceLabel); + } + + /** + * Takes in @param value representing the importance value + * @return a number of star string. + */ + public static String changeImportanceToStar(String value) { + int intValue = Integer.parseInt(value); + String starString = ""; + for (int i = 0; i < intValue; i++) { + starString = starString + '\u2605' + " "; + } + return starString; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof GoalCard)) { + return false; + } + + // state check + GoalCard card = (GoalCard) other; + return id.getText().equals(card.id.getText()) + && goal.equals(card.goal); + } +} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 20ad5fee906a..1fdbd953faa4 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -1,9 +1,13 @@ package seedu.address.ui; +import static seedu.address.model.ThemeColourUtil.getThemeHashMap; + +import java.util.HashMap; import java.util.logging.Logger; import com.google.common.eventbus.Subscribe; +import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.MenuItem; @@ -17,7 +21,9 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.ui.ExitAppRequestEvent; import seedu.address.commons.events.ui.ShowHelpRequestEvent; +import seedu.address.commons.events.ui.ThemeSwitchRequestEvent; import seedu.address.logic.Logic; +import seedu.address.model.AddressBook; import seedu.address.model.UserPrefs; /** @@ -27,20 +33,26 @@ public class MainWindow extends UiPart { private static final String FXML = "MainWindow.fxml"; + private static final int PERCENTAGE_KEY_NUMBER = 100; + private static final String EXTENSIONS_STYLESHEET = "view/Extensions.css"; + private static final String DEFAULT_THEME_COLOUR = "dark"; + private static final HashMap themeHashMap; private final Logger logger = LogsCenter.getLogger(this.getClass()); private Stage primaryStage; private Logic logic; + private AddressBook addressBook; // Independent Ui parts residing in this Ui container - private BrowserPanel browserPanel; + private CalendarPanel calendarPanel; private PersonListPanel personListPanel; private Config config; private UserPrefs prefs; + private String themeColour; @FXML - private StackPane browserPlaceholder; + private StackPane calendarPlaceholder; @FXML private StackPane commandBoxPlaceholder; @@ -57,6 +69,9 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + static { + themeHashMap = getThemeHashMap(); + } public MainWindow(Stage primaryStage, Config config, UserPrefs prefs, Logic logic) { super(FXML, primaryStage); @@ -67,9 +82,9 @@ public MainWindow(Stage primaryStage, Config config, UserPrefs prefs, Logic logi this.prefs = prefs; // Configure the UI + setThemeColour(); setTitle(config.getAppTitle()); setWindowDefaultSize(prefs); - setAccelerators(); registerAsAnEventHandler(this); } @@ -116,16 +131,17 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - browserPanel = new BrowserPanel(); - browserPlaceholder.getChildren().add(browserPanel.getRoot()); + calendarPanel = new CalendarPanel(logic.getFilteredReminderList(), logic.getFilteredPersonList()); + calendarPlaceholder.getChildren().add(calendarPanel.getRoot()); - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); + personListPanel = new PersonListPanel(logic.getFilteredPersonList(), logic.getFilteredGoalList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); ResultDisplay resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getAddressBookFilePath(), + calculateGoalCompletion()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(logic); @@ -185,13 +201,63 @@ public PersonListPanel getPersonListPanel() { return this.personListPanel; } - void releaseResources() { - browserPanel.freeResources(); - } - @Subscribe private void handleShowHelpEvent(ShowHelpRequestEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); handleHelp(); } + + //@@author deborahlow97 + /** + * Calculation of percentage of goal completed + * @return + */ + private int calculateGoalCompletion() { + int totalGoal = logic.getFilteredGoalList().size(); + int totalGoalCompleted = 0; + String completionStatus; + for (int i = 0; i < totalGoal; i++) { + completionStatus = logic.getFilteredGoalList().get(i).getCompletion().value; + totalGoalCompleted += isCompletedGoal(completionStatus); + } + int percentageGoalCompletion = (int) (((float) totalGoalCompleted / totalGoal) * PERCENTAGE_KEY_NUMBER); + return percentageGoalCompletion; + } + + /** + * @param completionStatus gives a String that should be either "true" or "false", indicating if the goal is + * completed. + * @return true or false + */ + private int isCompletedGoal(String completionStatus) { + int valueToAdd; + if (completionStatus.equals("true")) { + valueToAdd = 1; + } else { + valueToAdd = 0; + } + return valueToAdd; + } + + private void setThemeColour() { + setThemeColour(DEFAULT_THEME_COLOUR); + } + + private void setThemeColour(String themeColour) { + primaryStage.getScene().getStylesheets().add(EXTENSIONS_STYLESHEET); + primaryStage.getScene().getStylesheets().add(themeHashMap.get(themeColour)); + } + + private void changeThemeColour() { + primaryStage.getScene().getStylesheets().clear(); + setThemeColour(themeColour); + } + + @Subscribe + private void handleChangeThemeEvent(ThemeSwitchRequestEvent event) { + themeColour = event.themeToChangeTo; + Platform.runLater( + this::changeThemeColour + ); + } } diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index f6727ea83abd..dfa9ea39d3a2 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -1,18 +1,23 @@ package seedu.address.ui; +import java.util.Iterator; +import java.util.Set; + 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.person.Cca; import seedu.address.model.person.Person; /** * An UI component that displays information of a {@code Person}. */ public class PersonCard extends UiPart { - private static final String FXML = "PersonListCard.fxml"; + private static final String[] TAG_COLOR_STYLES = new String[]{"teal", "red", "yellow", "blue", "orange", "brown", + "green", "pink", "black", "purple"}; /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. @@ -33,9 +38,15 @@ public class PersonCard extends UiPart { @FXML private Label phone; @FXML - private Label address; + private Label birthday; + @FXML + private Label levelOfFriendship; @FXML - private Label email; + private Label unitNumber; + @FXML + private Label ccas; + @FXML + private Label meetDate; @FXML private FlowPane tags; @@ -45,11 +56,58 @@ public PersonCard(Person person, int displayedIndex) { id.setText(displayedIndex + ". "); name.setText(person.getName().fullName); phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + birthday.setText(person.getBirthday().value); + levelOfFriendship.setText(changeLevelOfFriendshipToHeart(person.getLevelOfFriendship().value)); + unitNumber.setText(person.getUnitNumber().value); + ccas.setText(getCcasInString(person.getCcas())); + meetDate.setText("Meet Date: " + person.getMeetDate().value); + initTags(person); + } + + //@@author deborahlow97 + /** + * Returns the color style for {@code tagName}'s label. + */ + private String getTagColorStyleFor(String tagName) { + // we use the hash code of the tag name to generate a random color, so that the color remain consistent + // between diffe 11rent runs of the program while still making it random enough between tags. + return TAG_COLOR_STYLES[Math.abs(tagName.hashCode()) % TAG_COLOR_STYLES.length]; + } + + /** + * Creates the tag labels for {@code person}. + */ + private void initTags(Person person) { + person.getTags().forEach(tag -> { + Label tagLabel = new Label(tag.tagName); + tagLabel.getStyleClass().add(getTagColorStyleFor(tag.tagName)); + tags.getChildren().add(tagLabel); + }); + } + + private String getCcasInString(Set ccas) { + String ccasValue = ""; + Iterator iterator = ccas.iterator(); + while (iterator.hasNext()) { + ccasValue = ccasValue + iterator.next().toString() + " "; + } + return ccasValue; + } + + /** + * Takes in @param value representing the level of friendship value + * @return a number of hearts string. + */ + public static String changeLevelOfFriendshipToHeart(String value) { + int intValue = Integer.parseInt(value); + String heartString = ""; + for (int i = 0; i < intValue; i++) { + heartString = heartString + '\u2665' + " "; + } + return heartString; } + //@@author @Override public boolean equals(Object other) { // short circuit if same object diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java index 60a4f70f4e71..4174e0b714e0 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/address/ui/PersonListPanel.java @@ -15,6 +15,7 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.ui.JumpToListRequestEvent; import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.address.model.goal.Goal; import seedu.address.model.person.Person; /** @@ -26,19 +27,25 @@ public class PersonListPanel extends UiPart { @FXML private ListView personListView; + @FXML + private ListView goalListView; - public PersonListPanel(ObservableList personList) { + public PersonListPanel(ObservableList personList, ObservableList goalList) { super(FXML); - setConnections(personList); + setConnections(personList, goalList); registerAsAnEventHandler(this); } - private void setConnections(ObservableList personList) { + private void setConnections(ObservableList personList, ObservableList goalList) { ObservableList mappedList = EasyBind.map( personList, (person) -> new PersonCard(person, personList.indexOf(person) + 1)); personListView.setItems(mappedList); personListView.setCellFactory(listView -> new PersonListViewCell()); setEventHandlerForSelectionChangeEvent(); + ObservableList mappedGoalList = EasyBind.map( + goalList, (goal) -> new GoalCard(goal, goalList.indexOf(goal) + 1)); + goalListView.setItems(mappedGoalList); + goalListView.setCellFactory(listView -> new GoalListViewCell()); } private void setEventHandlerForSelectionChangeEvent() { @@ -85,4 +92,38 @@ protected void updateItem(PersonCard person, boolean empty) { } } + //@@author deborahlow97 + @Subscribe + private void handleJumpToGoalListRequestEvent(JumpToListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollToGoal(event.targetIndex); + } + + /** + * Scrolls to the {@code GoalCard} at the {@code index} and selects it. + */ + private void scrollToGoal(int index) { + Platform.runLater(() -> { + goalListView.scrollTo(index); + goalListView.getSelectionModel().clearAndSelect(index); + }); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code GoalCard}. + */ + class GoalListViewCell extends ListCell { + + @Override + protected void updateItem(GoalCard goal, boolean empty) { + super.updateItem(goal, empty); + + if (empty || goal == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(goal.getRoot()); + } + } + } } diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java index 06fb7e50c935..995ff1c01ec3 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/address/ui/StatusBarFooter.java @@ -9,10 +9,12 @@ import com.google.common.eventbus.Subscribe; import javafx.application.Platform; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.layout.Region; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.model.goal.Goal; /** * A ui for the status bar that is displayed at the footer of the application. @@ -21,7 +23,7 @@ public class StatusBarFooter extends UiPart { public static final String SYNC_STATUS_INITIAL = "Not updated yet in this session"; public static final String SYNC_STATUS_UPDATED = "Last Updated: %s"; - + public static final int PERCENTAGE_KEY_NUMBER = 100; /** * Used to generate time stamps. * @@ -40,12 +42,14 @@ public class StatusBarFooter extends UiPart { private StatusBar syncStatus; @FXML private StatusBar saveLocationStatus; + @FXML + private StatusBar goalCompletionStatus; - - public StatusBarFooter(String saveLocation) { + public StatusBarFooter(String saveLocation, int goalCompletion) { super(FXML); setSyncStatus(SYNC_STATUS_INITIAL); setSaveLocation("./" + saveLocation); + setGoalCompletion(goalCompletion); registerAsAnEventHandler(this); } @@ -71,11 +75,44 @@ private void setSyncStatus(String status) { Platform.runLater(() -> this.syncStatus.setText(status)); } + //@@author deborahlow97 + private void setGoalCompletion(int goalCompletion) { + Platform.runLater(() -> this.goalCompletionStatus.setText("Goal " + goalCompletion + "% completed.")); + } + + public int getGoalCompletion(ObservableList goalList) { + int totalGoal = goalList.size(); + int totalGoalCompleted = 0; + String completionStatus; + for (int i = 0; i < totalGoal; i++) { + completionStatus = goalList.get(i).getCompletion().value; + totalGoalCompleted += isCompletedGoal(completionStatus); + } + int percentageGoalCompletion = (int) (((float) totalGoalCompleted / totalGoal) * PERCENTAGE_KEY_NUMBER); + return percentageGoalCompletion; + } + + /** + * @param completionStatus gives a String that should be either "true" or "false", indicating if the goal is + * completed. + * @return 1 or 0 + */ + private int isCompletedGoal(String completionStatus) { + int valueToAdd; + if (completionStatus.equals("true")) { + valueToAdd = 1; + } else { + valueToAdd = 0; + } + return valueToAdd; + } + //@@author @Subscribe public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { long now = clock.millis(); String lastUpdated = new Date(now).toString(); logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Setting last updated status to " + lastUpdated)); setSyncStatus(String.format(SYNC_STATUS_UPDATED, lastUpdated)); + setGoalCompletion(getGoalCompletion(abce.data.getGoalList())); } } diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index 3fd3c17be156..1994e78b7a69 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -30,7 +30,7 @@ public class UiManager extends ComponentManager implements Ui { public static final String FILE_OPS_ERROR_DIALOG_CONTENT_MESSAGE = "Could not save data to file"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/college_zone.jpeg"; private Logic logic; private Config config; @@ -66,7 +66,6 @@ public void start(Stage primaryStage) { public void stop() { prefs.updateLastUsedGuiSetting(mainWindow.getCurrentGuiSetting()); mainWindow.hide(); - mainWindow.releaseResources(); } private void showFileOperationAlertAndWait(String description, String details, Throwable cause) { diff --git a/src/main/resources/images/college_zone.jpeg b/src/main/resources/images/college_zone.jpeg new file mode 100644 index 000000000000..fd53eca7f66a Binary files /dev/null and b/src/main/resources/images/college_zone.jpeg differ diff --git a/src/main/resources/images/completedImage.png b/src/main/resources/images/completedImage.png new file mode 100644 index 000000000000..bf8a9f9d67d5 Binary files /dev/null and b/src/main/resources/images/completedImage.png differ diff --git a/src/main/resources/images/ongoingImage.png b/src/main/resources/images/ongoingImage.png new file mode 100644 index 000000000000..f23ea638f49d Binary files /dev/null and b/src/main/resources/images/ongoingImage.png differ diff --git a/src/main/resources/view/BubblegumTheme.css b/src/main/resources/view/BubblegumTheme.css new file mode 100644 index 000000000000..34a82ecbea19 --- /dev/null +++ b/src/main/resources/view/BubblegumTheme.css @@ -0,0 +1,415 @@ +/* @@author deborahlow97 */ +.background { + -fx-background-color: derive(#ffdae0, 20%); + background-color: #ffb6c1; +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Lato"; + -fx-text-fill: #FFFF99; + -fx-opacity: 0.9; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Lato"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Lato Light"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Lato"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #ffdae0; + -fx-control-inner-background: #ffdae0; + -fx-background-color: #ffdae0; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Lato"; + -fx-text-fill: black; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: derive(#ffdae0, 20%); + -fx-border-color: transparent transparent transparent #4d4d4d; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: derive(#ffdae0, 20%); +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: derive(#ffdae0, 20%); +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:filled:even { + -fx-background-color: #BFEFFF; +} + +.list-cell:filled:odd { + -fx-background-color: #63D1F4; +} + +.list-cell:filled:selected { + -fx-background-color: # 00BFFF; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #3e7b91; + -fx-border-width: 1; +} + +.list-cell .label { + -fx-text-fill: black; +} + +.cell_big_label { + -fx-font-family: "Lato"; + -fx-font-size: 18px; + -fx-text-fill: #010504; +} + +.cell_small_label { + -fx-font-family: "Lato"; + -fx-font-size: 13px; + -fx-text-fill: #010504; +} + +.anchor-pane { + -fx-background-color: derive(#ffdae0, 20%); +} + +.pane-with-border { + -fx-background-color: derive(#ffdae0, 20%); + -fx-border-color: derive(#ffdae0, 10%); + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: derive(#ffdae0, 20%); + -fx-text-fill: black; +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Lato Light"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +.result-display .label { + -fx-text-fill: black !important; +} + +.status-bar .label { + -fx-font-family: "Lato"; + -fx-text-fill: black; +} + +.status-bar-with-border { + -fx-background-color: derive(#ffdae0, 30%); + -fx-border-color: derive(#ffdae0, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: black; +} + +.grid-pane { + -fx-background-color: derive(#ffdae0, 30%); + -fx-border-color: derive(#ffdae0, 30%); + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: derive(#ffdae0, 30%); +} + +.context-menu { + -fx-background-color: derive(#ffdae0, 50%); +} + +.context-menu .label { + -fx-text-fill: black; +} + +.menu-bar { + -fx-background-color: derive(#ffdae0, 20%); +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Lato Light"; + -fx-text-fill: black; + -fx-opacity: 0.9; +} +/*a*/ +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #313131; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #ffdae0; + -fx-font-family: "Lato"; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #ebebeb; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: black; + -fx-text-fill: #ffdae0; +} + +.button:focused { + -fx-border-color: black, black; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #ffdae0; + -fx-text-fill: black; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffdae0; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: #ffdae0; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #ffdae0; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: black; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#ffdae0, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: black; + -fx-text-fill: black; +} + +.scroll-bar { + -fx-background-color: derive(#BA55D3, 20%); +} + +.scroll-bar .thumb { + -fx-background-color: derive(#EE82EE, 50%); + -fx-background-insets: 3; +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-padding: 0 0 0 0; +} + +.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { + -fx-shape: " "; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 1 8 1 8; +} + +.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { + -fx-padding: 8 1 8 1; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-width: 0; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#commandTextField { + -fx-background-color: transparent #ffb6c1 transparent #ffb6c1; + -fx-background-insets: 0; + -fx-border-color: #ffb6c1 #ffb6c1 #FF7F50 #ffb6c1; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Lato Light"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, white, 10, 0, 0, 0); +} + +#resultDisplay .content { + -fx-background-color: transparent, #ffb6c1, transparent, #ffb6c1; + -fx-background-radius: 0; +} + +#tags { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#tags .label { + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} + + +#tags .teal { + -fx-text-fill: white; + -fx-background-color: #3e7b91; +} + +#tags .red { + -fx-text-fill: black; + -fx-background-color: #ff7675; +} + +#tags .yellow { + -fx-background-color: #ffeaa7; + -fx-text-fill: black; +} + +#tags .blue { + -fx-text-fill: black; + -fx-background-color: #48dbfb; +} + +#tags .orange { + -fx-text-fill: black; + -fx-background-color: #ffa502; +} + +#tags .brown { + -fx-text-fill: black; + -fx-background-color: #D7ACAC; +} + +#tags .green { + -fx-text-fill: black; + -fx-background-color: #55efc4; +} + +#tags .pink { + -fx-text-fill: black; + -fx-background-color: #fd79a8; +} + +#tags .black { + -fx-text-fill: white; + -fx-background-color: black; +} + +#tags .purple { + -fx-text-fill: black; + -fx-background-color: #a29bfe; +} + +#importance { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#importance .label { + -fx-background-color: #FFE761; + -fx-text-fill: black; + -fx-padding: 1 3 1 3; + -fx-border-radius: 3; + -fx-background-radius: 2; + -fx-font-size: 13; +} diff --git a/src/main/resources/view/CalendarPanel.fxml b/src/main/resources/view/CalendarPanel.fxml new file mode 100644 index 000000000000..28a058ff535f --- /dev/null +++ b/src/main/resources/view/CalendarPanel.fxml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index d06336391cca..4b8cfbba5f31 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -5,28 +5,28 @@ .label { -fx-font-size: 11pt; - -fx-font-family: "Segoe UI Semibold"; + -fx-font-family: "Lato"; -fx-text-fill: #555555; -fx-opacity: 0.9; } .label-bright { -fx-font-size: 11pt; - -fx-font-family: "Segoe UI Semibold"; + -fx-font-family: "Lato"; -fx-text-fill: white; -fx-opacity: 1; } .label-header { -fx-font-size: 32pt; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Lato"; -fx-text-fill: white; -fx-opacity: 1; } .text-field { -fx-font-size: 12pt; - -fx-font-family: "Segoe UI Semibold"; + -fx-font-family: "Lato"; } .tab-pane { @@ -66,7 +66,7 @@ .table-view .column-header .label { -fx-font-size: 20pt; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Lato"; -fx-text-fill: white; -fx-alignment: center-left; -fx-opacity: 1; @@ -121,13 +121,13 @@ } .cell_big_label { - -fx-font-family: "Segoe UI Semibold"; - -fx-font-size: 16px; + -fx-font-family: "Lato"; + -fx-font-size: 18px; -fx-text-fill: #010504; } .cell_small_label { - -fx-font-family: "Segoe UI"; + -fx-font-family: "Lato"; -fx-font-size: 13px; -fx-text-fill: #010504; } @@ -149,7 +149,7 @@ .result-display { -fx-background-color: transparent; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Lato Light"; -fx-font-size: 13pt; -fx-text-fill: white; } @@ -159,7 +159,7 @@ } .status-bar .label { - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Lato"; -fx-text-fill: white; } @@ -197,7 +197,7 @@ .menu-bar .label { -fx-font-size: 14pt; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Lato Light"; -fx-text-fill: white; -fx-opacity: 0.9; } @@ -217,7 +217,7 @@ -fx-border-width: 2; -fx-background-radius: 0; -fx-background-color: #1d1d1d; - -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-family: "Lato"; -fx-font-size: 11pt; -fx-text-fill: #d8d8d8; -fx-background-insets: 0 0 0 0, 0, 1, 2; @@ -322,7 +322,7 @@ -fx-border-color: #383838 #383838 #ffffff #383838; -fx-border-insets: 0; -fx-border-width: 1; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Lato Light"; -fx-font-size: 13pt; -fx-text-fill: white; } @@ -342,10 +342,74 @@ } #tags .label { + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 13; +} + + +#tags .teal { -fx-text-fill: white; -fx-background-color: #3e7b91; +} + +#tags .red { + -fx-text-fill: black; + -fx-background-color: #ff7675; +} + +#tags .yellow { + -fx-background-color: #ffeaa7; + -fx-text-fill: black; +} + +#tags .blue { + -fx-text-fill: black; + -fx-background-color: #48dbfb; +} + +#tags .orange { + -fx-text-fill: black; + -fx-background-color: #ffa502; +} + +#tags .brown { + -fx-text-fill: black; + -fx-background-color: #D7ACAC; +} + +#tags .green { + -fx-text-fill: black; + -fx-background-color: #55efc4; +} + +#tags .pink { + -fx-text-fill: black; + -fx-background-color: #fd79a8; +} + +#tags .black { + -fx-text-fill: white; + -fx-background-color: black; +} + +#tags .purple { + -fx-text-fill: black; + -fx-background-color: #a29bfe; +} + +#importance { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#importance .label { + -fx-background-color: #FFDB58; + -fx-text-fill: black; -fx-padding: 1 3 1 3; - -fx-border-radius: 2; + -fx-border-radius: 3; -fx-background-radius: 2; - -fx-font-size: 11; + -fx-font-size: 14; } + diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css index bfe82a85964d..68c91ba4a992 100644 --- a/src/main/resources/view/Extensions.css +++ b/src/main/resources/view/Extensions.css @@ -1,3 +1,4 @@ +@import url(https://fonts.googleapis.com/css?family=Lato:Light,Medium,Semibold,Bold); .error { -fx-text-fill: #d06651 !important; /* The error class should always override the default text-fill style */ diff --git a/src/main/resources/view/GoalListCard.fxml b/src/main/resources/view/GoalListCard.fxml new file mode 100644 index 000000000000..fd7eb0720d58 --- /dev/null +++ b/src/main/resources/view/GoalListCard.fxml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/LightTheme.css b/src/main/resources/view/LightTheme.css new file mode 100644 index 000000000000..7370fd2e76d6 --- /dev/null +++ b/src/main/resources/view/LightTheme.css @@ -0,0 +1,415 @@ +/* @@author deborahlow97 */ +.background { + -fx-background-color: derive(#ffffff, 20%); + background-color: #f5f5f5; +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Lato"; + -fx-text-fill: #cecece; + -fx-opacity: 0.9; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Lato"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Lato Light"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Lato"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #ffffff; + -fx-control-inner-background: #ffffff; + -fx-background-color: #ffffff; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Lato"; + -fx-text-fill: black; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: derive(#ffffff, 20%); + -fx-border-color: transparent transparent transparent #4d4d4d; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: derive(#ffffff, 20%); +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: derive(#ffffff, 20%); +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:filled:even { + -fx-background-color: #ebebeb; +} + +.list-cell:filled:odd { + -fx-background-color: #fcf9f9; +} + +.list-cell:filled:selected { + -fx-background-color: #dae3f2; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #3e7b91; + -fx-border-width: 1; +} + +.list-cell .label { + -fx-text-fill: black; +} + +.cell_big_label { + -fx-font-family: "Lato"; + -fx-font-size: 18px; + -fx-text-fill: #010504; +} + +.cell_small_label { + -fx-font-family: "Lato"; + -fx-font-size: 13px; + -fx-text-fill: #010504; +} + +.anchor-pane { + -fx-background-color: derive(#ffffff, 20%); +} + +.pane-with-border { + -fx-background-color: derive(#ffffff, 20%); + -fx-border-color: derive(#ffffff, 10%); + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: derive(#ffffff, 20%); + -fx-text-fill: black; +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Lato Light"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +.result-display .label { + -fx-text-fill: black !important; +} + +.status-bar .label { + -fx-font-family: "Lato"; + -fx-text-fill: black; +} + +.status-bar-with-border { + -fx-background-color: derive(#ffffff, 30%); + -fx-border-color: derive(#ffffff, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: black; +} + +.grid-pane { + -fx-background-color: derive(#ffffff, 30%); + -fx-border-color: derive(#ffffff, 30%); + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: derive(#ffffff, 30%); +} + +.context-menu { + -fx-background-color: derive(#ffffff, 50%); +} + +.context-menu .label { + -fx-text-fill: black; +} + +.menu-bar { + -fx-background-color: derive(#ffffff, 20%); +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Lato Light"; + -fx-text-fill: black; + -fx-opacity: 0.9; +} +/*a*/ +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #313131; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #ffffff; + -fx-font-family: "Lato"; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #ebebeb; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: black; + -fx-text-fill: #ffffff; +} + +.button:focused { + -fx-border-color: black, black; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #ffffff; + -fx-text-fill: black; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #ffffff; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: black; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#ffffff, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: black; + -fx-text-fill: black; +} + +.scroll-bar { + -fx-background-color: derive(grey, 20%); +} + +.scroll-bar .thumb { + -fx-background-color: derive(#ffffff, 50%); + -fx-background-insets: 3; +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-padding: 0 0 0 0; +} + +.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { + -fx-shape: " "; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 1 8 1 8; +} + +.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { + -fx-padding: 8 1 8 1; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-width: 0; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#commandTextField { + -fx-background-color: transparent #f5f5f5 transparent #f5f5f5; + -fx-background-insets: 0; + -fx-border-color: #ffffff #ffffff #383838 #ffffff; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Lato Light"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, white, 10, 0, 0, 0); +} + +#resultDisplay .content { + -fx-background-color: transparent, #f5f5f5, transparent, #f5f5f5; + -fx-background-radius: 0; +} + +#tags { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#tags .label { + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} + + +#tags .teal { + -fx-text-fill: white; + -fx-background-color: #3e7b91; +} + +#tags .red { + -fx-text-fill: black; + -fx-background-color: #ff7675; +} + +#tags .yellow { + -fx-background-color: #ffeaa7; + -fx-text-fill: black; +} + +#tags .blue { + -fx-text-fill: black; + -fx-background-color: #48dbfb; +} + +#tags .orange { + -fx-text-fill: black; + -fx-background-color: #ffa502; +} + +#tags .brown { + -fx-text-fill: black; + -fx-background-color: #D7ACAC; +} + +#tags .green { + -fx-text-fill: black; + -fx-background-color: #55efc4; +} + +#tags .pink { + -fx-text-fill: black; + -fx-background-color: #fd79a8; +} + +#tags .black { + -fx-text-fill: white; + -fx-background-color: black; +} + +#tags .purple { + -fx-text-fill: black; + -fx-background-color: #a29bfe; +} + +#importance { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#importance .label { + -fx-background-color: #FFE761; + -fx-text-fill: black; + -fx-padding: 1 3 1 3; + -fx-border-radius: 3; + -fx-background-radius: 2; + -fx-font-size: 13; +} diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 1dadb95b6ffe..87172ee512b9 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -14,14 +14,10 @@ - + - - - - @@ -33,6 +29,7 @@ + @@ -54,7 +51,7 @@ - + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f08ea32ad558..365d09397bae 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -2,6 +2,7 @@ + @@ -12,7 +13,7 @@ - + @@ -29,8 +30,11 @@