diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..2faea6ca6c2 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -5,9 +5,10 @@ */ public class Messages { - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; + 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_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; + public static final String MESSAGE_INVALID_ACTIVITY_DISPLAY_INDEX = "The activity index provided is invalid!"; + public static final String MESSAGE_INVALID_PERSON_DISPLAY_INDEX = "The person index provided is invalid!"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; } diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 444f037f266..3c56cdf091e 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -1,6 +1,7 @@ package seedu.address.logic; import java.nio.file.Path; +import java.util.List; import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; @@ -37,6 +38,18 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of activities */ ObservableList getFilteredActivityList(); + /** + * Returns an unmodifiable list of {@code Person} containing all participants of a + * specified {@code Activity}. + */ + List getAssociatedPersons(Activity activity); + + /** + * Returns an unmodifiable list of {@code Activity} containing all activities a specified + * {@code Person} has participated in. + */ + List getAssociatedActivities(Person person); + /** * Returns the user prefs' address book file path. */ diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 725d3e26785..309f619b21b 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.List; import java.util.logging.Logger; import javafx.collections.ObservableList; @@ -73,6 +74,16 @@ public ObservableList getFilteredActivityList() { return model.getFilteredActivityList(); } + @Override + public List getAssociatedPersons(Activity activity) { + return model.getAssociatedPersons(activity); + } + + @Override + public List getAssociatedActivities(Person person) { + return model.getAssociatedActivities(person); + } + @Override public Path getAddressBookFilePath() { return model.getAddressBookFilePath(); diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 0e85c9f4b7d..205a50f6ed9 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -1,11 +1,12 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.util.Objects; import java.util.Optional; -import seedu.address.model.ContextType; +import seedu.address.model.Context; /** * Represents the result of a command execution. @@ -20,8 +21,8 @@ public class CommandResult { /** The application should exit. */ private final boolean exit; - /** Type of updated context - empty if context was not changed by executing this command. */ - private final Optional newContext; + /** Updated application context - empty if context was not changed by executing this command. */ + private final Optional newContext; /** * Constructs a {@code CommandResult} with the specified fields, for commands that does not change @@ -40,8 +41,8 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { * @param feedbackToUser {@code String} output from executing the command * @param newContext the new {@code ContextType} after executing the command */ - public CommandResult(String feedbackToUser, ContextType newContext) { - requireNonNull(newContext); + public CommandResult(String feedbackToUser, Context newContext) { + requireAllNonNull(feedbackToUser, newContext); this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = false; this.exit = false; @@ -68,7 +69,7 @@ public boolean isExit() { return exit; } - public Optional getUpdatedContext() { + public Optional getUpdatedContext() { return newContext; } @@ -94,5 +95,4 @@ public boolean equals(Object other) { public int hashCode() { return Objects.hash(feedbackToUser, showHelp, exit, newContext); } - } diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 56c5d40b895..e6b081368b7 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -36,7 +36,7 @@ public CommandResult execute(Model model) throws CommandException { List lastShownList = model.getFilteredPersonList(); if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAY_INDEX); } Person personToDelete = 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 3805e2f8372..8f7cde38f1d 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -71,7 +71,7 @@ public CommandResult execute(Model model) throws CommandException { List lastShownList = model.getFilteredPersonList(); if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAY_INDEX); } Person personToEdit = lastShownList.get(index.getZeroBased()); diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index b18d257de33..58e91cef03d 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -8,7 +8,6 @@ import seedu.address.logic.CommandSubType; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Context; -import seedu.address.model.ContextType; import seedu.address.model.Model; /** @@ -38,15 +37,22 @@ public ListCommand(CommandSubType type) { public CommandResult execute(Model model) throws CommandException { requireNonNull(model); + // Contextual behaviour switch (this.type) { case CONTACT: - model.setContext(Context.newListContactContext()); + Context newContactContext = Context.newListContactContext(); + + model.setContext(newContactContext); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_ENTRIES); - return new CommandResult(String.format(MESSAGE_SUCCESS, "contacts"), ContextType.LIST_CONTACT); + + return new CommandResult(String.format(MESSAGE_SUCCESS, "contacts"), newContactContext); case ACTIVITY: - model.setContext(Context.newListActivityContext()); + Context newActivityContext = Context.newListActivityContext(); + + model.setContext(newActivityContext); model.updateFilteredActivityList(PREDICATE_SHOW_ALL_ENTRIES); - return new CommandResult(String.format(MESSAGE_SUCCESS, "activities"), ContextType.LIST_ACTIVITY); + + return new CommandResult(String.format(MESSAGE_SUCCESS, "activities"), newActivityContext); default: throw new CommandException(MESSAGE_UNKNOWN_LIST_TYPE); } diff --git a/src/main/java/seedu/address/logic/commands/ViewCommand.java b/src/main/java/seedu/address/logic/commands/ViewCommand.java new file mode 100644 index 00000000000..5568b0c4c7d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ViewCommand.java @@ -0,0 +1,98 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ACTIVITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONTACT; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandSubType; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Context; +import seedu.address.model.Model; +import seedu.address.model.activity.Activity; +import seedu.address.model.person.Person; + +/** + * Updates the GUI to list all entries of a specified type to the user. + */ +public class ViewCommand extends Command { + + public static final String COMMAND_WORD = "view"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Switches the current view to show the details of a contact or activity, " + + "identified by their display index (a positive integer) in the respective list.\n" + + "Parameters: " + PREFIX_CONTACT + "CONTACT_INDEX OR " + PREFIX_ACTIVITY + "ACTIVITY_INDEX\n" + + "Example: view " + PREFIX_CONTACT + "1"; + + public static final String MESSAGE_SUCCESS = "Showing the details of %s %s"; + public static final String MESSAGE_UNKNOWN_VIEW_TYPE = "View command has unknown type!"; + + private final Index targetIndex; + private final CommandSubType type; + + public ViewCommand(CommandSubType type, Index targetIndex) { + requireAllNonNull(type, targetIndex); + this.type = type; + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + // Contextual behaviour + switch (this.type) { + case CONTACT: + List listedPersons = model.getFilteredPersonList(); + + if (targetIndex.getOneBased() > listedPersons.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAY_INDEX); + } + + Person personToView = listedPersons.get(targetIndex.getZeroBased()); + Context newContactContext = new Context(personToView); + model.setContext(newContactContext); + + return new CommandResult(String.format(MESSAGE_SUCCESS, "contact", personToView.getName()), + newContactContext); + case ACTIVITY: + List listedActivities = model.getFilteredActivityList(); + + if (targetIndex.getOneBased() > listedActivities.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ACTIVITY_DISPLAY_INDEX); + } + + Activity activityToView = listedActivities.get(targetIndex.getZeroBased()); + Context newActivityContext = new Context(activityToView); + model.setContext(newActivityContext); + + return new CommandResult(String.format(MESSAGE_SUCCESS, "activity", activityToView.getTitle()), + newActivityContext); + default: + throw new CommandException(MESSAGE_UNKNOWN_VIEW_TYPE); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ViewCommand)) { + return false; + } + + // state check + ViewCommand v = (ViewCommand) other; + return type.equals(v.type) + && targetIndex.equals(v.targetIndex); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index d1b5c318ff9..b28afdaedfb 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -19,6 +19,7 @@ import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.InviteCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.ViewCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -84,6 +85,9 @@ public Command parseCommand(String userInput) throws ParseException { case DisinviteCommand.COMMAND_WORD: return new DisinviteCommandParser().parse(arguments); + case ViewCommand.COMMAND_WORD: + return new ViewCommandParser().parse(arguments); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } diff --git a/src/main/java/seedu/address/logic/parser/ListCommandParser.java b/src/main/java/seedu/address/logic/parser/ListCommandParser.java index 4b87a6f8a3f..60d28be81d2 100644 --- a/src/main/java/seedu/address/logic/parser/ListCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/ListCommandParser.java @@ -34,6 +34,7 @@ public ListCommand parse(String args) throws ParseException { if (argMultimap.getValue(PREFIX_ACTIVITY).isPresent()) { return new ListCommand(CommandSubType.ACTIVITY); } else { + assert argMultimap.getValue(PREFIX_CONTACT).isPresent(); return new ListCommand(CommandSubType.CONTACT); } } diff --git a/src/main/java/seedu/address/logic/parser/ViewCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java new file mode 100644 index 00000000000..65bacaa7778 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java @@ -0,0 +1,65 @@ +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_ACTIVITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONTACT; + +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandSubType; +import seedu.address.logic.commands.ViewCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new {@code ViewCommand} object + */ +public class ViewCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of a {@code ViewCommand} + * and returns a {@code ViewCommand} object for execution. + * @throws ParseException if the user input does not conform to the expected format, + * or has missing compulsory arguments. + */ + public ViewCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_CONTACT, PREFIX_ACTIVITY); + + if (!onePrefixPresent(argMultimap, PREFIX_CONTACT, PREFIX_ACTIVITY) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE)); + } + + CommandSubType subType; + String indexString; + + if (argMultimap.getValue(PREFIX_ACTIVITY).isPresent()) { + subType = CommandSubType.ACTIVITY; + indexString = argMultimap.getValue(PREFIX_ACTIVITY).get(); + } else { + assert argMultimap.getValue(PREFIX_CONTACT).isPresent(); + subType = CommandSubType.CONTACT; + indexString = argMultimap.getValue(PREFIX_CONTACT).get(); + } + + try { + Index index = ParserUtil.parseIndex(indexString); + return new ViewCommand(subType, index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE), pe); + } + } + + /** + * Returns true if exactly one the prefixes contains a non-empty {@code Optional} value in the given + * {@code ArgumentMultimap}. + */ + private static boolean onePrefixPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes) + .filter(prefix -> argumentMultimap.getValue(prefix).isPresent()) + .count() == 1; + } +} diff --git a/src/main/java/seedu/address/model/Context.java b/src/main/java/seedu/address/model/Context.java index bf1d5264cc0..f8380be7af9 100644 --- a/src/main/java/seedu/address/model/Context.java +++ b/src/main/java/seedu/address/model/Context.java @@ -2,6 +2,7 @@ import static java.util.Objects.requireNonNull; +import java.util.Objects; import java.util.Optional; import seedu.address.model.activity.Activity; @@ -18,7 +19,7 @@ public class Context { /** * Default constructor where context type is MAIN. */ - Context() { + public Context() { this.object = Optional.empty(); this.type = ContextType.MAIN; } @@ -40,7 +41,7 @@ public Context(Activity activity) { /** * Constructor for a VIEW_CONTACT context. */ - Context(Person person) { + public Context(Person person) { requireNonNull(person); object = Optional.ofNullable(person); type = ContextType.VIEW_CONTACT; @@ -72,6 +73,11 @@ public Optional getContact() { return object.filter(x -> type == ContextType.VIEW_CONTACT).map(x->(Person) x); } + @Override + public int hashCode() { + return Objects.hash(type, object); + } + @Override public boolean equals(Object other) { if (this == other) { diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index f6cc15c246c..756b8beaf5a 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -2,6 +2,7 @@ import java.nio.file.Path; import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.function.Predicate; @@ -175,4 +176,15 @@ public interface Model { */ void updateFilteredActivityList(Predicate predicate); + /** + * Returns an unmodifiable list of {@code Person} containing all participants of a specified + * {@code Activity}, for GUI purposes. + */ + List getAssociatedPersons(Activity activity); + + /** + * Returns an unmodifiable list of {@code Activity} containing all activities a specified {@code Person} + * has participated in, for GUI purposes. + */ + List getAssociatedActivities(Person person); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 59ed78712af..2e0b52715c0 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -5,9 +5,11 @@ import java.nio.file.Path; import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.function.Predicate; import java.util.logging.Logger; +import java.util.stream.Collectors; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; @@ -253,6 +255,26 @@ public void updateFilteredActivityList(Predicate predicate) { filteredActivities.setPredicate(predicate); } + // =========== Association lookup accessors for GUI ============================================ + + @Override + public List getAssociatedPersons(Activity activity) { + requireNonNull(activity); + + return this.addressBook.getPersonList().stream() + .filter((person) -> activity.hasPerson(person.getPrimaryKey())) + .collect(Collectors.toUnmodifiableList()); + } + + @Override + public List getAssociatedActivities(Person person) { + requireNonNull(person); + + return this.activityBook.getActivityList().stream() + .filter((activity) -> activity.hasPerson(person.getPrimaryKey())) + .collect(Collectors.toUnmodifiableList()); + } + // =========== Overridden Java methods ========================================================= @Override @@ -277,5 +299,4 @@ public boolean equals(Object obj) { && filteredPersons.equals(other.filteredPersons) && filteredActivities.equals(other.filteredActivities); } - } diff --git a/src/main/java/seedu/address/model/activity/Activity.java b/src/main/java/seedu/address/model/activity/Activity.java index 6e723b6bd88..a31bf93d128 100644 --- a/src/main/java/seedu/address/model/activity/Activity.java +++ b/src/main/java/seedu/address/model/activity/Activity.java @@ -1,10 +1,11 @@ package seedu.address.model.activity; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static java.util.Objects.requireNonNull; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Objects; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -41,7 +42,7 @@ public class Activity { * @param ids The people participating in the activity. */ public Activity(int primaryKey, Title title, Integer ... ids) { - requireAllNonNull(title); + requireNonNull(title); participantIds = new ArrayList<>(ids.length); participantActive = new ArrayList<>(ids.length); idDict = new HashMap<>(ids.length); @@ -53,6 +54,7 @@ public Activity(int primaryKey, Title title, Integer ... ids) { this.title = title; invite(ids); } + /** Constructor for Activity. Sets primary key automatically. * @param title Title of the activity. @@ -98,6 +100,14 @@ public Title getTitle() { return title; } + /** + * Returns a List containing all the IDs of the participants. + * @return A {@code List} containing the IDs of all participants. + */ + public List getParticipantsIds() { + return participantIds; + } + /** * Gets the transfer matrix. * @return The matrix. Every (i, j) entry reflects how much i receives from @@ -109,6 +119,23 @@ public ArrayList> getTransferMatrix() { return transferMatrix; } + /** + * Returns the aggregate amount owed to a specified participant in this activity. A + * negative amount indicates this participant owes other participants. + * @param participantId {@code Integer} ID of the participant. + */ + public Double getTransferAmount(Integer participantId) { + requireNonNull(participantId); + + Integer participantIndex = idDict.get(participantId); + assert participantIndex != null; + + simplifyExpenses(); + + ArrayList transfers = transferMatrix.get(participantIndex); + return transfers.stream().reduce(0.0, (acc, amt) -> acc + amt); + } + /** * Invite people to the activity. * @param people The people that will be added into the activity. diff --git a/src/main/java/seedu/address/ui/ActivityCard.java b/src/main/java/seedu/address/ui/ActivityCard.java index 21543b767bf..07e09096dc8 100644 --- a/src/main/java/seedu/address/ui/ActivityCard.java +++ b/src/main/java/seedu/address/ui/ActivityCard.java @@ -2,7 +2,6 @@ import javafx.fxml.FXML; import javafx.scene.control.Label; -import javafx.scene.layout.HBox; import javafx.scene.layout.Region; import seedu.address.model.activity.Activity; @@ -10,21 +9,10 @@ * An UI component that displays information of an {@code Activity}. */ public class ActivityCard extends UiPart { - private static final String FXML = "ActivityCard.fxml"; - /** - * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. - * As a consequence, UI elements' variable names cannot be set to such keywords - * or an exception will be thrown by JavaFX during runtime. - * - * @see The issue on AddressBook level 4 - */ - public final Activity activity; - @FXML - private HBox cardPane; @FXML private Label index; @FXML @@ -39,9 +27,10 @@ public ActivityCard(Activity activity, int displayedIndex) { this.activity = activity; id.setText("ID: " + activity.getPrimaryKey()); index.setText("#" + displayedIndex); - title.setText(activity.getTitle().title); - int numParticipants = activity.getParticipantIds().size() + 1; - participantCount.setText(numParticipants + (numParticipants > 1 ? " participants" : " participant")); + title.setText(activity.getTitle().toString()); + + int numParticipants = activity.getParticipantIds().size(); + participantCount.setText(numParticipants + (numParticipants != 1 ? " participants" : " participant")); } @Override diff --git a/src/main/java/seedu/address/ui/ActivityDetailsPanel.java b/src/main/java/seedu/address/ui/ActivityDetailsPanel.java new file mode 100644 index 00000000000..bcafb8ab797 --- /dev/null +++ b/src/main/java/seedu/address/ui/ActivityDetailsPanel.java @@ -0,0 +1,88 @@ +package seedu.address.ui; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import seedu.address.model.activity.Activity; +import seedu.address.model.person.Person; + +/** + * Panel displaying details of a contact. + */ +public class ActivityDetailsPanel extends UiPart { + private static final String FXML = "ActivityDetailsPanel.fxml"; + + public final Activity activity; + + @FXML + private ScrollPane detailsPane; + @FXML + private Label title; + @FXML + private FlowPane participantTags; + @FXML + private Label participantCount; + @FXML + private Label spending; + @FXML + private VBox expenseHistory; + @FXML + private VBox transferList; + + public ActivityDetailsPanel(Activity viewedActivity, List participants) { + super(FXML); + this.activity = viewedActivity; + + title.setText(activity.getTitle().toString()); + + participants.stream() + .map((participant) -> participant.getName().toString()) + .forEach(name -> participantTags.getChildren().add(new Label(name))); + + int numParticipants = activity.getParticipantIds().size(); + participantCount.setText(numParticipants + (numParticipants != 1 ? " participants" : " participant")); + + double totalSpending = activity.getExpenses().stream() + .map((expense) -> expense.getAmount().value) + .reduce(0.00, (acc, amt) -> acc + amt); + spending.setText(String.format("$%.2f", totalSpending)); + + activity.getExpenses().stream() + .forEach(expense -> { + expenseHistory.getChildren().add(new ExpenseCard(expense, participants).getRoot()); + }); + + List participantIds = activity.getParticipantIds(); + Map idMapping = participants.stream() + .collect(Collectors.toMap(p -> p.getPrimaryKey(), p -> p)); + + ArrayList> transfersMatrix = activity.getTransferMatrix(); + for (int i = 0; i < numParticipants; i++) { + ArrayList row = transfersMatrix.get(i); + for (int j = i; j < numParticipants; j++) { + // i and j do not owe each other any amount + if (row.get(j) == 0.0) { + continue; + } + + Person personI = idMapping.get(participantIds.get(i)); + Person personJ = idMapping.get(participantIds.get(j)); + if (row.get(j) < 0) { + // i owes j some amount (i --> j) + transferList.getChildren().add(new TransferCard(personI, personJ, -row.get(j)).getRoot()); + } else { + // j owes i some amount (j --> i) + transferList.getChildren().add(new TransferCard(personJ, personI, row.get(j)).getRoot()); + } + } + } + } +} diff --git a/src/main/java/seedu/address/ui/ActivityHistoryCard.java b/src/main/java/seedu/address/ui/ActivityHistoryCard.java new file mode 100644 index 00000000000..4687619b618 --- /dev/null +++ b/src/main/java/seedu/address/ui/ActivityHistoryCard.java @@ -0,0 +1,56 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import seedu.address.model.activity.Activity; + +/** + * An UI component that displays contact-specific information of an {@code Activity}. + */ +public class ActivityHistoryCard extends UiPart { + private static final String FXML = "ActivityHistoryCard.fxml"; + + public final Activity activity; + public final double transferAmt; + + @FXML + private Label title; + @FXML + private Label netTransfer; + + public ActivityHistoryCard(Activity activity, double transferAmt) { + super(FXML); + this.activity = activity; + this.transferAmt = transferAmt; + + title.setText(activity.getTitle().toString()); + if (transferAmt < 0) { + netTransfer.setStyle("-fx-text-fill: maroon"); + netTransfer.setText(String.format("Owes $%.2f", -transferAmt)); + } else if (transferAmt > 0) { + netTransfer.setStyle("-fx-text-fill: darkgreen"); + netTransfer.setText(String.format("Owed $%.2f", transferAmt)); + } else { + netTransfer.setText("Not involved in any expenses yet."); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ActivityHistoryCard)) { + return false; + } + + // state check + ActivityHistoryCard card = (ActivityHistoryCard) other; + return activity.equals(card.activity) + && transferAmt == card.transferAmt; + } +} diff --git a/src/main/java/seedu/address/ui/ActivityListPanel.java b/src/main/java/seedu/address/ui/ActivityListPanel.java new file mode 100644 index 00000000000..6f34bc59e0a --- /dev/null +++ b/src/main/java/seedu/address/ui/ActivityListPanel.java @@ -0,0 +1,46 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.activity.Activity; + +/** + * Panel containing the list of activities. + */ +public class ActivityListPanel extends UiPart { + private static final String FXML = "ListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(ActivityListPanel.class); + + @FXML + private ListView listView; + + public ActivityListPanel(ObservableList activityList) { + super(FXML); + logger.info("Created ActivityListPanel to list activity entries."); + listView.setItems(activityList); + listView.setCellFactory(listView -> new ListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of an {@code Activity} entry with an {@code ActivityCard}. + */ + class ListViewCell extends ListCell { + @Override + protected void updateItem(Activity activity, boolean empty) { + super.updateItem(activity, empty); + + if (empty || activity == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new ActivityCard(activity, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/ExpenseCard.java b/src/main/java/seedu/address/ui/ExpenseCard.java new file mode 100644 index 00000000000..88aff851558 --- /dev/null +++ b/src/main/java/seedu/address/ui/ExpenseCard.java @@ -0,0 +1,89 @@ +package seedu.address.ui; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import seedu.address.model.activity.Expense; +import seedu.address.model.person.Person; + +/** + * An UI component that displays activity-specific information of an {@code Expense}. + */ +public class ExpenseCard extends UiPart { + private static final String FXML = "ExpenseCard.fxml"; + + public final Expense expense; + + @FXML + private VBox detailsContainer; + @FXML + private Label description; + @FXML + private Label paidBy; + @FXML + private FlowPane sharedBy; + @FXML + private Label amount; + + public ExpenseCard(Expense expense, List activityParticipants) { + super(FXML); + this.expense = expense; + + String expenseDescription = expense.getDescription(); + if (expenseDescription.length() == 0) { + // Remove the description label if this expense has no description + detailsContainer.getChildren().remove(0); + } else { + description.setText(expense.getDescription()); + } + + amount.setText(String.format("$%s", expense.getAmount().toString())); + // If the expense was soft-deleted, strike out the amount + if (expense.isDeleted()) { + amount.setStyle("-fx-strikethrough: true"); + } + + Optional expenseOwnerOpt = activityParticipants.stream() + .filter((participant) -> participant.getPrimaryKey() == expense.getPersonId()) + .findFirst(); + assert expenseOwnerOpt.isPresent(); + + Person expenseOwner = expenseOwnerOpt.get(); + // Expense owner's label always appears first in the FlowPane and is coloured differently + paidBy.setText(String.format("Paid: %s", expenseOwner.getName().toString())); + + Set involvedIds = Arrays.stream(expense.getInvolved()) + .boxed() + .collect(Collectors.toUnmodifiableSet()); + + activityParticipants.stream() + .filter((participant) -> involvedIds.contains(participant.getPrimaryKey())) + .map((participant) -> participant.getName().toString()) + .forEach(name -> sharedBy.getChildren().add(new Label(name))); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ExpenseCard)) { + return false; + } + + // state check + ExpenseCard card = (ExpenseCard) other; + return expense.equals(card.expense); + } +} diff --git a/src/main/java/seedu/address/ui/ListPanel.java b/src/main/java/seedu/address/ui/ListPanel.java deleted file mode 100644 index a0d0bddd5c0..00000000000 --- a/src/main/java/seedu/address/ui/ListPanel.java +++ /dev/null @@ -1,49 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.activity.Activity; -import seedu.address.model.person.Person; - -/** - * Panel containing a list of entries to display. - */ -public class ListPanel extends UiPart { - private static final String FXML = "ListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(ListPanel.class); - - @FXML - private ListView listView; - - public ListPanel(ObservableList personList) { - super(FXML); - listView.setItems(personList); - listView.setCellFactory(listView -> new ListViewCell()); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class ListViewCell extends ListCell { - @Override - protected void updateItem(U entry, boolean empty) { - super.updateItem(entry, empty); - - if (empty || entry == null) { - setGraphic(null); - setText(null); - } else if (entry instanceof Person) { - setGraphic(new PersonCard((Person) entry, getIndex() + 1).getRoot()); - } else if (entry instanceof Activity) { - setGraphic(new ActivityCard((Activity) entry, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9412de56f7a..6b5191e72d6 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -1,5 +1,8 @@ package seedu.address.ui; +import static java.util.Objects.requireNonNull; + +import java.util.List; import java.util.Optional; import java.util.logging.Logger; @@ -12,6 +15,7 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Context; import seedu.address.model.ContextType; import seedu.address.model.activity.Activity; import seedu.address.model.person.Person; @@ -30,8 +34,10 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private ListPanel personListPanel; - private ListPanel activityListPanel; + private PersonListPanel personListPanel; + private ActivityListPanel activityListPanel; + private PersonDetailsPanel personDetailsPanel; + private ActivityDetailsPanel activityDetailsPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; @@ -80,8 +86,8 @@ public Stage getPrimaryStage() { * Fills up all the containers of this window. */ void fillInnerParts() { - personListPanel = new ListPanel(logic.getFilteredPersonList()); - activityListPanel = new ListPanel(logic.getFilteredActivityList()); + personListPanel = new PersonListPanel(logic.getFilteredPersonList()); + activityListPanel = new ActivityListPanel(logic.getFilteredActivityList()); // Show contacts by default contentContainer.getChildren().add(personListPanel.getRoot()); @@ -141,26 +147,42 @@ private void handleExit() { * {@code ContextType}. * @param newContext the {@code ContextType} of the updated GUI view */ - private void contextSwitch(ContextType newContext) { + private void contextSwitch(Context newContext) { + requireNonNull(newContext); + contentContainer.getChildren().clear(); - switch (newContext) { + ContextType newContextType = newContext.getType(); + + switch (newContextType) { case LIST_ACTIVITY: contentContainer.getChildren().add(activityListPanel.getRoot()); break; case LIST_CONTACT: contentContainer.getChildren().add(personListPanel.getRoot()); break; + case VIEW_CONTACT: + Person viewedContact = newContext.getContact().get(); + List associatedActivities = logic.getAssociatedActivities(viewedContact); + personDetailsPanel = new PersonDetailsPanel(viewedContact, associatedActivities); + contentContainer.getChildren().add(personDetailsPanel.getRoot()); + break; + case VIEW_ACTIVITY: + Activity viewedActivity = newContext.getActivity().get(); + List associatedPersons = logic.getAssociatedPersons(viewedActivity); + activityDetailsPanel = new ActivityDetailsPanel(viewedActivity, associatedPersons); + contentContainer.getChildren().add(activityDetailsPanel.getRoot()); + break; default: // Do nothing (leave content container empty) } } - public ListPanel getPersonListPanel() { + public PersonListPanel getPersonListPanel() { return personListPanel; } - public ListPanel getActivityListPanel() { + public ActivityListPanel getActivityListPanel() { return activityListPanel; } @@ -183,7 +205,7 @@ private CommandResult executeCommand(String commandText) throws CommandException handleExit(); } - Optional newContext = commandResult.getUpdatedContext(); + Optional newContext = commandResult.getUpdatedContext(); if (newContext.isPresent()) { logger.info("Updated context: " + newContext.get().toString()); contextSwitch(newContext.get()); diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 19d0608f8f1..26c482276fb 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -5,7 +5,6 @@ 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.Person; @@ -13,7 +12,6 @@ * An UI component that displays information of a {@code Person}. */ public class PersonCard extends UiPart { - private static final String FXML = "PersonCard.fxml"; /** @@ -26,8 +24,6 @@ public class PersonCard extends UiPart { public final Person person; - @FXML - private HBox cardPane; @FXML private Label index; @FXML @@ -44,8 +40,8 @@ public PersonCard(Person person, int displayedIndex) { this.person = person; id.setText("ID: " + person.getPrimaryKey()); index.setText("#" + displayedIndex); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); + name.setText(person.getName().toString()); + phone.setText(person.getPhone().toString()); person.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); diff --git a/src/main/java/seedu/address/ui/PersonDetailsPanel.java b/src/main/java/seedu/address/ui/PersonDetailsPanel.java new file mode 100644 index 00000000000..ea08cf404f5 --- /dev/null +++ b/src/main/java/seedu/address/ui/PersonDetailsPanel.java @@ -0,0 +1,56 @@ +package seedu.address.ui; + +import java.util.Comparator; +import java.util.List; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import seedu.address.model.activity.Activity; +import seedu.address.model.person.Person; + +/** + * Panel displaying details of a contact. + */ +public class PersonDetailsPanel extends UiPart { + private static final String FXML = "PersonDetailsPanel.fxml"; + + public final Person person; + + @FXML + private ScrollPane detailsPane; + @FXML + private Label name; + @FXML + private Label phone; + @FXML + private Label email; + @FXML + private Label address; + @FXML + private FlowPane tags; + @FXML + private VBox activityHistory; + + public PersonDetailsPanel(Person viewedPerson, List activities) { + super(FXML); + this.person = viewedPerson; + + name.setText(person.getName().toString()); + phone.setText(person.getPhone().toString()); + email.setText(person.getEmail().toString()); + address.setText(person.getAddress().toString()); + person.getTags().stream() + .sorted(Comparator.comparing(tag -> tag.tagName)) + .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + + activities.stream() + .forEach(activity -> { + Double transferAmount = activity.getTransferAmount(person.getPrimaryKey()); + activityHistory.getChildren().add(new ActivityHistoryCard(activity, transferAmount).getRoot()); + }); + } +} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java new file mode 100644 index 00000000000..f96dd6542c4 --- /dev/null +++ b/src/main/java/seedu/address/ui/PersonListPanel.java @@ -0,0 +1,46 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.person.Person; + +/** + * Panel containing the list of persons. + */ +public class PersonListPanel extends UiPart { + private static final String FXML = "ListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); + + @FXML + private ListView listView; + + public PersonListPanel(ObservableList personList) { + super(FXML); + logger.info("Created PersonListPanel to list person entries."); + listView.setItems(personList); + listView.setCellFactory(listView -> new ListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Person} entry with a {@code PersonCard}. + */ + class ListViewCell extends ListCell { + @Override + protected void updateItem(Person person, boolean empty) { + super.updateItem(person, empty); + + if (empty || person == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/address/ui/ResultDisplay.java index 7d98e84eedf..00c46178d28 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/seedu/address/ui/ResultDisplay.java @@ -10,7 +10,6 @@ * A ui for the status bar that is displayed at the header of the application. */ public class ResultDisplay extends UiPart { - private static final String FXML = "ResultDisplay.fxml"; @FXML diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java index 7e17911323f..bc78afc877e 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/address/ui/StatusBarFooter.java @@ -11,7 +11,6 @@ * A ui for the status bar that is displayed at the footer of the application. */ public class StatusBarFooter extends UiPart { - private static final String FXML = "StatusBarFooter.fxml"; @FXML diff --git a/src/main/java/seedu/address/ui/TransferCard.java b/src/main/java/seedu/address/ui/TransferCard.java new file mode 100644 index 00000000000..326be577324 --- /dev/null +++ b/src/main/java/seedu/address/ui/TransferCard.java @@ -0,0 +1,56 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import seedu.address.model.person.Person; + +/** + * An UI component that displays a required transfer between two participants in an + * {@code Activity}. + */ +public class TransferCard extends UiPart { + private static final String FXML = "TransferCard.fxml"; + + public final Person source; + public final Person destination; + public final double amount; + + @FXML + private Label fromPerson; + @FXML + private Label toPerson; + @FXML + private Label transferAmt; + + public TransferCard(Person source, Person destination, double amount) { + super(FXML); + + this.source = source; + this.destination = destination; + this.amount = amount; + + fromPerson.setText(source.getName().toString()); + toPerson.setText(destination.getName().toString()); + transferAmt.setText(String.format("$%.2f", amount)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TransferCard)) { + return false; + } + + // state check + TransferCard card = (TransferCard) other; + return source.equals(card.source) + && destination.equals(card.destination) + && amount == card.amount; + } +} diff --git a/src/main/resources/images/address-icon.png b/src/main/resources/images/address-icon.png new file mode 100644 index 00000000000..0936d4000e4 Binary files /dev/null and b/src/main/resources/images/address-icon.png differ diff --git a/src/main/resources/images/email-icon.png b/src/main/resources/images/email-icon.png new file mode 100644 index 00000000000..13b1d238fa6 Binary files /dev/null and b/src/main/resources/images/email-icon.png differ diff --git a/src/main/resources/images/participant-count-icon.png b/src/main/resources/images/participant-count-icon.png new file mode 100644 index 00000000000..bd1ca5de50c Binary files /dev/null and b/src/main/resources/images/participant-count-icon.png differ diff --git a/src/main/resources/images/participants-icon.png b/src/main/resources/images/participants-icon.png new file mode 100644 index 00000000000..fae537b3d4e Binary files /dev/null and b/src/main/resources/images/participants-icon.png differ diff --git a/src/main/resources/images/profile-placeholder.png b/src/main/resources/images/person-placeholder.png similarity index 100% rename from src/main/resources/images/profile-placeholder.png rename to src/main/resources/images/person-placeholder.png diff --git a/src/main/resources/images/phone-icon.png b/src/main/resources/images/phone-icon.png new file mode 100644 index 00000000000..0de651e1747 Binary files /dev/null and b/src/main/resources/images/phone-icon.png differ diff --git a/src/main/resources/images/spending-icon.png b/src/main/resources/images/spending-icon.png new file mode 100644 index 00000000000..5050588df81 Binary files /dev/null and b/src/main/resources/images/spending-icon.png differ diff --git a/src/main/resources/images/transfer-icon.png b/src/main/resources/images/transfer-icon.png new file mode 100644 index 00000000000..192348c9597 Binary files /dev/null and b/src/main/resources/images/transfer-icon.png differ diff --git a/src/main/resources/view/ActivityCard.fxml b/src/main/resources/view/ActivityCard.fxml index 8f9b3849ac0..926557c94df 100644 --- a/src/main/resources/view/ActivityCard.fxml +++ b/src/main/resources/view/ActivityCard.fxml @@ -1,6 +1,5 @@ - @@ -13,8 +12,8 @@ - - + + diff --git a/src/main/resources/view/ActivityDetailsPanel.fxml b/src/main/resources/view/ActivityDetailsPanel.fxml new file mode 100644 index 00000000000..d9f19617521 --- /dev/null +++ b/src/main/resources/view/ActivityDetailsPanel.fxml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ActivityHistoryCard.fxml b/src/main/resources/view/ActivityHistoryCard.fxml new file mode 100644 index 00000000000..00c3cc31b7e --- /dev/null +++ b/src/main/resources/view/ActivityHistoryCard.fxml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ExpenseCard.fxml b/src/main/resources/view/ExpenseCard.fxml new file mode 100644 index 00000000000..74a397ed118 --- /dev/null +++ b/src/main/resources/view/ExpenseCard.fxml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/LightTheme.css b/src/main/resources/view/LightTheme.css index b5b699a7383..1af1987479d 100644 --- a/src/main/resources/view/LightTheme.css +++ b/src/main/resources/view/LightTheme.css @@ -52,6 +52,7 @@ .content-display, .card-list { /* Suppress blue focus ring for result display */ + -fx-background-color: transparent; -fx-focus-color: transparent; -fx-background-insets: 1, 1, 1, 1; -fx-border-color: #c0c0c0; @@ -66,6 +67,10 @@ -fx-highlight-fill: lightblue; } +/* + * ================ LIST CARD COMPONENTS ================ + */ + .person-card, .activity-card { -fx-padding: 8; -fx-background-color: transparent; @@ -79,7 +84,6 @@ } .person-card .person-phone { - -fx-padding: 2 0 0 0; -fx-font-size: 15px; } @@ -97,6 +101,7 @@ } .tag-list { + -fx-padding: 0 0 2 0; -fx-hgap: 5; -fx-vgap: 3; } @@ -112,6 +117,173 @@ -fx-text-fill: #e0e0e0; } +/* + * ================ DISPLAY PANEL COMPONENTS ================ + */ + +.details-container { + /* Suppress blue focus ring for result display */ + -fx-background-color: #dbdbdb; + -fx-focus-color: transparent; +} + +.person-details, .activity-details, +.history-container .expense-history { + /* Add horizontal divider to visually separate details of contact from activity listings */ + -fx-border-color: linear-gradient(from 0% 0% to 100% 0%, #dbdbdb, #ababab, #dbdbdb); + -fx-border-width: 0 0 2px 0; +} + +.person-details .data-container, .activity-details .data-container { + -fx-padding: 8 8 8 4; +} + +.person-details .image-container, .activity-details .image-container { + /* Add internal padding */ + -fx-padding: 8 4 8 16; +} + +.person-details .person-name, .activity-details .activity-title { + -fx-font-family: "Noto Sans CJK SC Black", "Segoe UI", sans-serif; + -fx-font-weight: bolder; + -fx-font-size: 22px; + -fx-text-alignment: center; +} + +.person-details .tag-list, .activity-details .activity-title, +.activity-details .activity-participant-count-container, +.activity-details .participant-list, .history-container .expense-history { + -fx-padding: 0 0 4 0; +} + +.person-details .person-phone-container { + -fx-padding: 0 0 2 0; +} + +.person-details .person-phone, .activity-details .activity-spending { + -fx-font-size: 18px; + -fx-text-alignment: center; +} + +.person-details .person-address, .person-details .person-email, +.activity-details .activity-participant-count { + -fx-font-size: 15px; + -fx-text-alignment: center; +} + +.history-container, .transfers-container { + -fx-padding: 4 2 0 2; + -fx-alignment: center; +} + +.history-container .activity-history { + -fx-padding: 0 0 8 0; +} + +.history-container .activity-history-header, .history-container .expense-history-header, +.transfers-container .activity-transfers-header { + -fx-padding: 2 8 2 8; + -fx-background-color: #cbcbcb; + -fx-border-color: #c0c0c0; + -fx-border-radius: 1px; + -fx-border-width: 2px; + -fx-font-size: 14px; + -fx-text-alignment: center; +} + +.activity-history-card, .expense-card, .transfer-card { + -fx-padding: 4 0 4 0; + -fx-border-color: #c0c0c0; + -fx-border-radius: 1px; + -fx-border-width: 2px; + -fx-background-color: transparent; +} + +.activity-history-card .image-container { + -fx-padding: 5; +} + +.activity-history-card .details-container, .expense-card .amount-container, +.expense-card .details-container { + -fx-padding: 2 5 2 5; +} + +.activity-history-card .activity-title, .expense-card .expense-description { + -fx-font-family: "Noto Sans CJK SC Bold", "Segoe UI", sans-serif; + -fx-font-weight: bold; + -fx-font-size: 15px; + -fx-text-alignment: center; +} + +.activity-history-card .activity-transfer-amt { + -fx-font-size: 15px; +} + +.activity-details .participant-list, +.expense-card .expense-sharing-list { + -fx-hgap: 4; + -fx-vgap: 4; +} + +.activity-details .participant-list .label, +.expense-card .expense-sharing-list .label, +.transfer-card .transfer-from, .transfer-card .transfer-to { + -fx-background-color: royalblue; + -fx-padding: 2 4 2 4; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 12px; + -fx-font-family: "Noto Sans CJK SC Bold", "Segoe UI", sans-serif; + -fx-font-weight: bold; + -fx-text-fill: #ffffff; +} + +.expense-card .expense-amount { + -fx-font-family: "Noto Sans CJK SC Bold", "Segoe UI", sans-serif; + -fx-font-weight: bold; + -fx-font-size: 22px; + -fx-text-alignment: center; +} + +.expense-card .expense-sharing-list .label, +.transfer-card .transfer-from { + -fx-background-color: maroon; + -fx-font-size: 10px; +} + +.expense-card .expense-sharing-list .expense-owner, +.transfer-card .transfer-to { + -fx-background-color: darkgreen; + -fx-font-size: 10px; +} + +.expense-card .expense-owner { + -fx-text-alignment: center; +} + +.transfer-card .transfer-from-container, .transfer-card .transfer-to-container { + -fx-padding: 0 4 0 4; +} + +.transfer-card .transfer-amt-container { + -fx-padding: 0 4 4 4; +} + +.transfer-card .transfer-amount { + -fx-font-family: "Noto Sans CJK SC Bold", "Segoe UI", sans-serif; + -fx-font-weight: bold; + -fx-font-size: 14px; + -fx-text-alignment: center; +} + +.transfer-card .label { + -fx-text-alignment: center; +} + +/* + * ================ STATUS BAR FOOTER COMPONENT ================ + */ + .status-bar-root { -fx-padding: 10 8 4 8; /* Add horizontal divider to visually separate content from status bar */ @@ -128,4 +300,3 @@ -fx-font-size: 9pt; -fx-text-fill: black; } - diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 2065bc2f9bb..d3d9f6b2739 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -25,7 +25,7 @@ - + diff --git a/src/main/resources/view/PersonCard.fxml b/src/main/resources/view/PersonCard.fxml index 030221ecacd..22be982845f 100644 --- a/src/main/resources/view/PersonCard.fxml +++ b/src/main/resources/view/PersonCard.fxml @@ -1,6 +1,5 @@ - @@ -11,7 +10,7 @@ - + @@ -19,9 +18,9 @@ - - + diff --git a/src/main/resources/view/PersonDetailsPanel.fxml b/src/main/resources/view/PersonDetailsPanel.fxml new file mode 100644 index 00000000000..6423dfe7b0d --- /dev/null +++ b/src/main/resources/view/PersonDetailsPanel.fxml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/TransferCard.fxml b/src/main/resources/view/TransferCard.fxml new file mode 100644 index 00000000000..e750be3c0ec --- /dev/null +++ b/src/main/resources/view/TransferCard.fxml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java index 266d8f883e7..15d70fa635b 100644 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ b/src/test/java/seedu/address/logic/LogicManagerTest.java @@ -1,7 +1,7 @@ package seedu.address.logic; import static org.junit.jupiter.api.Assertions.assertEquals; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_PERSON_DISPLAY_INDEX; import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; @@ -70,7 +70,7 @@ public void execute_invalidCommandFormat_throwsParseException() { @Test public void execute_commandExecutionError_throwsCommandException() { String deleteCommand = "delete 9"; - assertCommandException(deleteCommand, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + assertCommandException(deleteCommand, MESSAGE_INVALID_PERSON_DISPLAY_INDEX); } @Test diff --git a/src/test/java/seedu/address/logic/commands/CommandResultTest.java b/src/test/java/seedu/address/logic/commands/CommandResultTest.java index 29e58c89c2e..631935e5f62 100644 --- a/src/test/java/seedu/address/logic/commands/CommandResultTest.java +++ b/src/test/java/seedu/address/logic/commands/CommandResultTest.java @@ -7,7 +7,9 @@ import org.junit.jupiter.api.Test; -import seedu.address.model.ContextType; +import seedu.address.model.Context; +import seedu.address.testutil.TypicalActivities; +import seedu.address.testutil.TypicalPersons; public class CommandResultTest { @Test @@ -36,16 +38,20 @@ public void equals() { // different exit value -> returns false assertFalse(commandResult.equals(new CommandResult("feedback", false, true))); - CommandResult contextualResult = new CommandResult("output", ContextType.LIST_ACTIVITY); + CommandResult contextualResult = new CommandResult("output", new Context(TypicalPersons.ALICE)); // identity -> returns true - assertTrue(contextualResult.equals(new CommandResult("output", ContextType.LIST_ACTIVITY))); + assertTrue(contextualResult.equals(new CommandResult("output", new Context(TypicalPersons.ALICE)))); - // empty context -> returns false - assertFalse(contextualResult.equals(commandResult)); + // empty Context -> returns false + assertFalse(contextualResult.equals(new CommandResult("output"))); - // non-empty but different context -> returns false - assertFalse(contextualResult.equals(new CommandResult("output", ContextType.VIEW_ACTIVITY))); + // different ContextType -> returns false + assertFalse(contextualResult.equals(new CommandResult("output", Context.newListContactContext()))); + assertFalse(contextualResult.equals(new CommandResult("output", new Context(TypicalActivities.BREAKFAST)))); + + // same ContextType but different value -> returns false + assertFalse(contextualResult.equals(new CommandResult("output", new Context(TypicalPersons.BOB)))); } @Test @@ -64,15 +70,20 @@ public void hashcode() { // different exit value -> returns different hashcode assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false, true).hashCode()); - CommandResult contextualResult = new CommandResult("feedback", ContextType.VIEW_ACTIVITY); + CommandResult contextualResult = new CommandResult("feedback", new Context(TypicalActivities.BREAKFAST)); + int expectedHash = contextualResult.hashCode(); // identity -> returns same hashcode - assertEquals(contextualResult.hashCode(), - new CommandResult("feedback", ContextType.VIEW_ACTIVITY).hashCode()); + assertEquals(expectedHash, new CommandResult("feedback", new Context(TypicalActivities.BREAKFAST)).hashCode()); + + // empty Context -> returns false + assertNotEquals(expectedHash, new CommandResult("feedback").hashCode()); - // different context type -> returns different hashcode - assertNotEquals(contextualResult.hashCode(), - new CommandResult("feedback", ContextType.VIEW_CONTACT).hashCode()); + // different ContextType -> returns different hashcode + assertNotEquals(expectedHash, new CommandResult("feedback", Context.newListActivityContext()).hashCode()); + assertNotEquals(expectedHash, new CommandResult("feedback", new Context(TypicalPersons.ALICE)).hashCode()); + // same ContextType but different value -> returns false + assertNotEquals(expectedHash, new CommandResult("feedback", new Context(TypicalActivities.LUNCH)).hashCode()); } } diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index c7feec96d9f..a6e26ff6dba 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -129,6 +129,7 @@ public static void assertCommandFailure(Command command, Model actualModel, Stri assertEquals(expectedAddressBook, actualModel.getAddressBook()); assertEquals(expectedFilteredList, actualModel.getFilteredPersonList()); } + /** * Updates {@code model}'s filtered list to show only the person at the given {@code targetIndex} in the * {@code model}'s address book. @@ -142,5 +143,4 @@ public static void showPersonAtIndex(Model model, Index targetIndex) { assertEquals(1, model.getFilteredPersonList().size()); } - } diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java index 0142ecc5e4f..33dec544445 100644 --- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java @@ -5,8 +5,8 @@ import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import org.junit.jupiter.api.Test; @@ -21,8 +21,7 @@ import seedu.address.model.person.Person; /** - * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for - * {@code DeleteCommand}. + * Contains integration tests (interaction with the Model) and unit tests for {@code DeleteCommand}. */ public class DeleteCommandTest { @@ -31,8 +30,8 @@ public class DeleteCommandTest { @Test public void execute_validIndexUnfilteredList_success() { - Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON); + Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()); + DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST); String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); @@ -49,15 +48,15 @@ public void execute_invalidIndexUnfilteredList_throwsCommandException() { Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex); - assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAY_INDEX); } @Test public void execute_validIndexFilteredList_success() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(model, INDEX_FIRST); - Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON); + Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()); + DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST); String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, personToDelete); @@ -72,27 +71,27 @@ public void execute_validIndexFilteredList_success() { @Test public void execute_invalidIndexFilteredList_throwsCommandException() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(model, INDEX_FIRST); - Index outOfBoundIndex = INDEX_SECOND_PERSON; + Index outOfBoundIndex = INDEX_SECOND; // ensures that outOfBoundIndex is still in bounds of address book list assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex); - assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAY_INDEX); } @Test public void equals() { - DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON); - DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SECOND_PERSON); + DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST); + DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SECOND); // same object -> returns true assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); // same values -> returns true - DeleteCommand deleteFirstCommandCopy = new DeleteCommand(INDEX_FIRST_PERSON); + DeleteCommand deleteFirstCommandCopy = new DeleteCommand(INDEX_FIRST); assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); // different types -> returns false diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java index e0076b4abf0..35e39cdd58c 100644 --- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java @@ -10,8 +10,8 @@ import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import org.junit.jupiter.api.Test; @@ -30,7 +30,7 @@ import seedu.address.testutil.PersonBuilder; /** - * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for EditCommand. + * Contains integration tests (interaction with the Model) and unit tests for EditCommand. */ public class EditCommandTest { @@ -41,7 +41,7 @@ public class EditCommandTest { public void execute_allFieldsSpecifiedUnfilteredList_success() { Person editedPerson = new PersonBuilder().build(); EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build(); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor); + EditCommand editCommand = new EditCommand(INDEX_FIRST, descriptor); String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); @@ -77,8 +77,8 @@ public void execute_someFieldsSpecifiedUnfilteredList_success() { @Test public void execute_noFieldSpecifiedUnfilteredList_success() { - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, new EditPersonDescriptor()); - Person editedPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + EditCommand editCommand = new EditCommand(INDEX_FIRST, new EditPersonDescriptor()); + Person editedPerson = model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()); String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); @@ -90,11 +90,11 @@ public void execute_noFieldSpecifiedUnfilteredList_success() { @Test public void execute_filteredList_success() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(model, INDEX_FIRST); - Person personInFilteredList = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person personInFilteredList = model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()); Person editedPerson = new PersonBuilder(personInFilteredList).withName(VALID_NAME_BOB).build(); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, + EditCommand editCommand = new EditCommand(INDEX_FIRST, new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, editedPerson); @@ -108,20 +108,20 @@ public void execute_filteredList_success() { @Test public void execute_duplicatePersonUnfilteredList_failure() { - Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST.getZeroBased()); EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstPerson).build(); - EditCommand editCommand = new EditCommand(INDEX_SECOND_PERSON, descriptor); + EditCommand editCommand = new EditCommand(INDEX_SECOND, descriptor); assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON); } @Test public void execute_duplicatePersonFilteredList_failure() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(model, INDEX_FIRST); // edit person in filtered list into a duplicate in address book - Person personInList = model.getAddressBook().getPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); - EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, + Person personInList = model.getAddressBook().getPersonList().get(INDEX_SECOND.getZeroBased()); + EditCommand editCommand = new EditCommand(INDEX_FIRST, new EditPersonDescriptorBuilder(personInList).build()); assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON); @@ -133,7 +133,7 @@ public void execute_invalidPersonIndexUnfilteredList_failure() { EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build(); EditCommand editCommand = new EditCommand(outOfBoundIndex, descriptor); - assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAY_INDEX); } /** @@ -142,24 +142,24 @@ public void execute_invalidPersonIndexUnfilteredList_failure() { */ @Test public void execute_invalidPersonIndexFilteredList_failure() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); - Index outOfBoundIndex = INDEX_SECOND_PERSON; + showPersonAtIndex(model, INDEX_FIRST); + Index outOfBoundIndex = INDEX_SECOND; // ensures that outOfBoundIndex is still in bounds of address book list assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); EditCommand editCommand = new EditCommand(outOfBoundIndex, new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); - assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAY_INDEX); } @Test public void equals() { - final EditCommand standardCommand = new EditCommand(INDEX_FIRST_PERSON, DESC_AMY); + final EditCommand standardCommand = new EditCommand(INDEX_FIRST, DESC_AMY); // same values -> returns true EditPersonDescriptor copyDescriptor = new EditPersonDescriptor(DESC_AMY); - EditCommand commandWithSameValues = new EditCommand(INDEX_FIRST_PERSON, copyDescriptor); + EditCommand commandWithSameValues = new EditCommand(INDEX_FIRST, copyDescriptor); assertTrue(standardCommand.equals(commandWithSameValues)); // same object -> returns true @@ -172,10 +172,9 @@ public void equals() { assertFalse(standardCommand.equals(new ClearCommand())); // different index -> returns false - assertFalse(standardCommand.equals(new EditCommand(INDEX_SECOND_PERSON, DESC_AMY))); + assertFalse(standardCommand.equals(new EditCommand(INDEX_SECOND, DESC_AMY))); // different descriptor -> returns false - assertFalse(standardCommand.equals(new EditCommand(INDEX_FIRST_PERSON, DESC_BOB))); + assertFalse(standardCommand.equals(new EditCommand(INDEX_FIRST, DESC_BOB))); } - } diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/address/logic/commands/ListCommandTest.java index 5a425343304..9987fbb21cb 100644 --- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ListCommandTest.java @@ -1,10 +1,12 @@ package seedu.address.logic.commands; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; import static seedu.address.testutil.Assert.assertThrows; import static seedu.address.testutil.TypicalActivities.getTypicalActivityBook; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; import org.junit.jupiter.api.BeforeEach; @@ -12,7 +14,6 @@ import seedu.address.logic.CommandSubType; import seedu.address.model.Context; -import seedu.address.model.ContextType; import seedu.address.model.InternalState; import seedu.address.model.Model; import seedu.address.model.ModelManager; @@ -37,7 +38,7 @@ public void setUp() { model.getAddressBook(), new UserPrefs(), new InternalState(), getTypicalActivityBook()); expectedModel.setContext(Context.newListContactContext()); expectedMessage = String.format(ListCommand.MESSAGE_SUCCESS, "contacts"); - expectedResult = new CommandResult(expectedMessage, ContextType.LIST_CONTACT); + expectedResult = new CommandResult(expectedMessage, Context.newListContactContext()); } @Test @@ -53,7 +54,7 @@ public void execute_listIsNotFiltered_showsSameList() { @Test public void execute_listIsFiltered_showsEverything() { - showPersonAtIndex(model, INDEX_FIRST_PERSON); + showPersonAtIndex(model, INDEX_FIRST); assertCommandSuccess(new ListCommand(CommandSubType.CONTACT), model, expectedResult, expectedModel); } @@ -62,7 +63,7 @@ public void execute_listIsFiltered_showsEverything() { public void execute_activityListIsFiltered_showsEverything() { expectedModel.setContext(Context.newListActivityContext()); expectedMessage = String.format(ListCommand.MESSAGE_SUCCESS, "activities"); - expectedResult = new CommandResult(expectedMessage, ContextType.LIST_ACTIVITY); + expectedResult = new CommandResult(expectedMessage, Context.newListActivityContext()); model.updateFilteredActivityList((activity) -> activity.getTitle().equals(new Title("Lunch"))); @@ -70,4 +71,22 @@ public void execute_activityListIsFiltered_showsEverything() { assertCommandSuccess(new ListCommand(CommandSubType.ACTIVITY), model, expectedResult, expectedModel); } + + @Test + public void equals() { + ListCommand listPersons = new ListCommand(CommandSubType.CONTACT); + ListCommand listActivities = new ListCommand(CommandSubType.ACTIVITY); + + // identity -> returns true + assertTrue(listPersons.equals(listPersons)); + + // same values -> returns true + assertTrue(listActivities.equals(new ListCommand(CommandSubType.ACTIVITY))); + + // different CommandSubType -> returns false + assertFalse(listPersons.equals(listActivities)); + + // null -> returns false + assertFalse(listActivities.equals(null)); + } } diff --git a/src/test/java/seedu/address/logic/commands/ViewCommandTest.java b/src/test/java/seedu/address/logic/commands/ViewCommandTest.java new file mode 100644 index 00000000000..7af9d4e9af4 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/ViewCommandTest.java @@ -0,0 +1,131 @@ +package seedu.address.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; +import static seedu.address.testutil.Assert.assertThrows; +import static seedu.address.testutil.TypicalActivities.getTypicalActivityBook; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIFTH; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND; +import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandSubType; +import seedu.address.model.Context; +import seedu.address.model.InternalState; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.activity.Activity; +import seedu.address.model.person.Person; + +/** + * Contains integration tests (interaction with the Model) and unit tests for ViewCommand. + */ +public class ViewCommandTest { + + private Model model; + private Model expectedModel; + private String expectedMessage; + private CommandResult expectedResult; + + @BeforeEach + public void setUp() { + model = new ModelManager( + getTypicalAddressBook(), new UserPrefs(), new InternalState(), getTypicalActivityBook()); + model.setContext(Context.newListContactContext()); + expectedModel = new ModelManager( + model.getAddressBook(), new UserPrefs(), new InternalState(), getTypicalActivityBook()); + expectedModel.setContext(Context.newListContactContext()); + } + + @Test + public void constructor_calledWithNullArgument_throwsCommandException() { + assertThrows(NullPointerException.class, () -> new ViewCommand(null, INDEX_FIRST)); + assertThrows(NullPointerException.class, () -> new ViewCommand(CommandSubType.ACTIVITY, null)); + } + + @Test + public void execute_validIndexUnfilteredList_success() { + model.setContext(Context.newListActivityContext()); + Activity activityToView = model.getFilteredActivityList().get(INDEX_SECOND.getZeroBased()); + + Context newActivityContext = new Context(activityToView); + expectedModel.setContext(newActivityContext); + expectedMessage = String.format(ViewCommand.MESSAGE_SUCCESS, "activity", activityToView.getTitle()); + expectedResult = new CommandResult(expectedMessage, newActivityContext); + + assertCommandSuccess(new ViewCommand(CommandSubType.ACTIVITY, INDEX_SECOND), + model, expectedResult, expectedModel); + } + + @Test + public void execute_validIndexFilteredList_success() { + // Filter the list to only show the second person (Bob) + Person personToView = model.getFilteredPersonList().get(INDEX_SECOND.getZeroBased()); + showPersonAtIndex(model, INDEX_SECOND); + + showPersonAtIndex(expectedModel, INDEX_SECOND); + Context newContactContext = new Context(personToView); + expectedModel.setContext(newContactContext); + expectedMessage = String.format(ViewCommand.MESSAGE_SUCCESS, "contact", personToView.getName()); + expectedResult = new CommandResult(expectedMessage, newContactContext); + + assertCommandSuccess(new ViewCommand(CommandSubType.CONTACT, INDEX_FIRST), + model, expectedResult, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + model.setContext(Context.newListActivityContext()); + expectedModel.setContext(Context.newListActivityContext()); + + Index invalidIndex = Index.fromOneBased(model.getFilteredActivityList().size() + 1); + ViewCommand invalidCommand = new ViewCommand(CommandSubType.ACTIVITY, invalidIndex); + + assertCommandFailure(invalidCommand, model, Messages.MESSAGE_INVALID_ACTIVITY_DISPLAY_INDEX); + // Current Context should not change if command failed + assertEquals(model.getContext(), expectedModel.getContext()); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + // Filter the list to only show the fifth person (Elle) + showPersonAtIndex(model, INDEX_FIFTH); + + ViewCommand invalidCommand = new ViewCommand(CommandSubType.CONTACT, INDEX_SECOND); + + assertCommandFailure(invalidCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAY_INDEX); + // Current Context should not change if command failed + assertEquals(model.getContext(), expectedModel.getContext()); + } + + @Test + public void equals() { + ViewCommand viewFirstActivity = new ViewCommand(CommandSubType.ACTIVITY, INDEX_FIRST); + ViewCommand viewFifthPerson = new ViewCommand(CommandSubType.CONTACT, INDEX_FIFTH); + + // identity -> returns true + assertTrue(viewFirstActivity.equals(viewFirstActivity)); + + // same values -> returns true + assertTrue(viewFifthPerson.equals(new ViewCommand(CommandSubType.CONTACT, INDEX_FIFTH))); + + // different CommandSubType -> returns false + assertFalse(viewFirstActivity.equals(viewFifthPerson)); + + // null -> returns false + assertFalse(viewFifthPerson.equals(null)); + + // same CommandSubType but different index -> returns false + assertFalse(viewFirstActivity.equals(new ViewCommand(CommandSubType.ACTIVITY, INDEX_SECOND))); + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java index 5ae0fdd098c..830c5133c7a 100644 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java @@ -5,7 +5,7 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import java.util.Arrays; import java.util.List; @@ -48,8 +48,8 @@ public void parseCommand_clear() throws Exception { @Test public void parseCommand_delete() throws Exception { DeleteCommand command = (DeleteCommand) parser.parseCommand( - DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); - assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), command); + DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST.getOneBased()); + assertEquals(new DeleteCommand(INDEX_FIRST), command); } @Test @@ -57,8 +57,8 @@ public void parseCommand_edit() throws Exception { Person person = new PersonBuilder().build(); EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(person).build(); EditCommand command = (EditCommand) parser.parseCommand(EditCommand.COMMAND_WORD + " " - + INDEX_FIRST_PERSON.getOneBased() + " " + PersonUtil.getEditPersonDescriptorDetails(descriptor)); - assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command); + + INDEX_FIRST.getOneBased() + " " + PersonUtil.getEditPersonDescriptorDetails(descriptor)); + assertEquals(new EditCommand(INDEX_FIRST, descriptor), command); } @Test diff --git a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java index 27eaec84450..75763b1a46a 100644 --- a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java @@ -3,7 +3,7 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import org.junit.jupiter.api.Test; @@ -22,7 +22,7 @@ public class DeleteCommandParserTest { @Test public void parse_validArgs_returnsDeleteCommand() { - assertParseSuccess(parser, "1", new DeleteCommand(INDEX_FIRST_PERSON)); + assertParseSuccess(parser, "1", new DeleteCommand(INDEX_FIRST)); } @Test diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java index 2ff31522486..a79956ddea3 100644 --- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java @@ -27,9 +27,9 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD; import org.junit.jupiter.api.Test; @@ -107,7 +107,7 @@ public void parse_invalidValue_failure() { @Test public void parse_allFieldsSpecified_success() { - Index targetIndex = INDEX_SECOND_PERSON; + Index targetIndex = INDEX_SECOND; String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + TAG_DESC_HUSBAND + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NAME_DESC_AMY + TAG_DESC_FRIEND; @@ -121,7 +121,7 @@ public void parse_allFieldsSpecified_success() { @Test public void parse_someFieldsSpecified_success() { - Index targetIndex = INDEX_FIRST_PERSON; + Index targetIndex = INDEX_FIRST; String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + EMAIL_DESC_AMY; EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) @@ -134,7 +134,7 @@ public void parse_someFieldsSpecified_success() { @Test public void parse_oneFieldSpecified_success() { // name - Index targetIndex = INDEX_THIRD_PERSON; + Index targetIndex = INDEX_THIRD; String userInput = targetIndex.getOneBased() + NAME_DESC_AMY; EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY).build(); EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); @@ -167,7 +167,7 @@ public void parse_oneFieldSpecified_success() { @Test public void parse_multipleRepeatedFields_acceptsLast() { - Index targetIndex = INDEX_FIRST_PERSON; + Index targetIndex = INDEX_FIRST; String userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_FRIEND + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_FRIEND + PHONE_DESC_BOB + ADDRESS_DESC_BOB + EMAIL_DESC_BOB + TAG_DESC_HUSBAND; @@ -183,7 +183,7 @@ public void parse_multipleRepeatedFields_acceptsLast() { @Test public void parse_invalidValueFollowedByValidValue_success() { // no other valid values specified - Index targetIndex = INDEX_FIRST_PERSON; + Index targetIndex = INDEX_FIRST; String userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + PHONE_DESC_BOB; EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB).build(); EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); @@ -200,7 +200,7 @@ public void parse_invalidValueFollowedByValidValue_success() { @Test public void parse_resetTags_success() { - Index targetIndex = INDEX_THIRD_PERSON; + Index targetIndex = INDEX_THIRD; String userInput = targetIndex.getOneBased() + TAG_EMPTY; EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withTags().build(); diff --git a/src/test/java/seedu/address/logic/parser/ListCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ListCommandParserTest.java index 0fe3699110d..b28e1e26abe 100644 --- a/src/test/java/seedu/address/logic/parser/ListCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/ListCommandParserTest.java @@ -26,13 +26,13 @@ public void parse_listTypeMissing_failure() { assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); // All irrelevant fields - assertParseFailure(parser, " " + PREFIX_TITLE + VALID_ACTIVITY_TITLE + NAME_DESC_AMY, + assertParseFailure(parser, " " + PREFIX_TITLE + VALID_ACTIVITY_TITLE + " " + NAME_DESC_AMY, String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); } @Test public void parse_bothListTypesPresent_failure() { - // Both type fields c/ and a/ + // Both type fields c/ and a/ present assertParseFailure(parser, " " + PREFIX_CONTACT + " " + PREFIX_ACTIVITY, String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); } @@ -55,7 +55,7 @@ public void parse_preamblePresent_failure() { } @Test - public void parse_oneListSubTypeWithArg_successWithArgIgnored() { + public void parse_oneListTypeWithArg_successWithArgIgnored() { // Type field a/ with non-empty arg value assertParseSuccess(parser, " " + PREFIX_ACTIVITY + VALID_PHONE_AMY, new ListCommand(CommandSubType.ACTIVITY)); diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index f47116675b2..6bccbef7776 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -4,7 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX; import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST; import java.util.Arrays; import java.util.Collections; @@ -55,10 +55,10 @@ public void parseIndex_outOfRangeInput_throwsParseException() { @Test public void parseIndex_validInput_success() throws Exception { // No whitespaces - assertEquals(INDEX_FIRST_PERSON, ParserUtil.parseIndex("1")); + assertEquals(INDEX_FIRST, ParserUtil.parseIndex("1")); // Leading and trailing whitespaces - assertEquals(INDEX_FIRST_PERSON, ParserUtil.parseIndex(" 1 ")); + assertEquals(INDEX_FIRST, ParserUtil.parseIndex(" 1 ")); } @Test diff --git a/src/test/java/seedu/address/logic/parser/ViewCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ViewCommandParserTest.java new file mode 100644 index 00000000000..836208ad1c5 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/ViewCommandParserTest.java @@ -0,0 +1,77 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.CommandTestUtil.PARTICIPANT_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ACTIVITY_TITLE; +import static seedu.address.logic.commands.CommandTestUtil.VALID_AMOUNT_DESC; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; +import static seedu.address.logic.commands.ViewCommand.MESSAGE_USAGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ACTIVITY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CONTACT; + +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.jupiter.api.Test; + +import seedu.address.logic.CommandSubType; +import seedu.address.logic.commands.ViewCommand; +import seedu.address.testutil.TypicalIndexes; + +public class ViewCommandParserTest { + private ViewCommandParser parser = new ViewCommandParser(); + + @Test + public void parse_viewTypeMissing_failure() { + // Empty command + assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + + // All irrelevant fields + assertParseFailure(parser, VALID_AMOUNT_DESC + " " + PARTICIPANT_DESC_AMY, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + + @Test + public void parse_bothViewTypesPresent_failure() { + // Both type fields c/ and a/ present + assertParseFailure(parser, " " + PREFIX_CONTACT + "20 " + PREFIX_ACTIVITY + "40", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + + @Test + public void parse_oneViewTypeWithoutValidArg_failure() { + // Type field c/ with non-empty arg value + assertParseFailure(parser, " " + PREFIX_CONTACT, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + + // Type field a/ with string as arg value + assertParseFailure(parser, " " + PREFIX_ACTIVITY + VALID_ACTIVITY_TITLE, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + + // Type field c/ with non-positive integer as arg value + assertParseFailure(parser, " " + PREFIX_CONTACT + "0", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } + + @Test + public void parse_oneViewTypePresentWithValidArg_success() { + // Only type field c/ + assertParseSuccess(parser, " " + PREFIX_ACTIVITY + "3", + new ViewCommand(CommandSubType.ACTIVITY, TypicalIndexes.INDEX_THIRD)); + + // Multiple of type field c/ + assertParseSuccess(parser, " " + PREFIX_CONTACT + "0 " + PREFIX_CONTACT + "2 " + PREFIX_CONTACT + "1", + new ViewCommand(CommandSubType.CONTACT, TypicalIndexes.INDEX_FIRST)); + + // Multiple of type field c/ - last arg is valid + assertParseSuccess(parser, " " + PREFIX_ACTIVITY + VALID_ACTIVITY_TITLE + " " + PREFIX_ACTIVITY + "2", + new ViewCommand(CommandSubType.ACTIVITY, TypicalIndexes.INDEX_SECOND)); + } + + @Test + public void parse_preamblePresent_failure() { + // Irrelevant field (preamble) followed by type field + assertParseFailure(parser, VALID_PHONE_AMY + " " + PREFIX_CONTACT + "2103", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/model/ContextTest.java b/src/test/java/seedu/address/model/ContextTest.java index 0ab88e6f682..fc7ee677344 100644 --- a/src/test/java/seedu/address/model/ContextTest.java +++ b/src/test/java/seedu/address/model/ContextTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Optional; @@ -60,4 +61,19 @@ public void equals() { // same ContextType but different Person -> returns false assertFalse(viewPersonContext.equals(new Context(TypicalPersons.BENSON))); } + + @Test + public void hashcode() { + Context listContext = Context.newListActivityContext(); + Context viewContext = new Context(TypicalPersons.ALICE); + + // same Context values -> returns same hashcode + assertEquals(listContext.hashCode(), Context.newListActivityContext().hashCode()); + assertEquals(viewContext.hashCode(), new Context(TypicalPersons.ALICE).hashCode()); + + // different Context values -> returns different hashcodes + assertNotEquals(listContext.hashCode(), Context.newListContactContext().hashCode()); + assertNotEquals(viewContext.hashCode(), new Context(TypicalPersons.AMY).hashCode()); + assertNotEquals(listContext.hashCode(), viewContext.hashCode()); + } } diff --git a/src/test/java/seedu/address/model/activity/ActivityTest.java b/src/test/java/seedu/address/model/activity/ActivityTest.java index b287c3683e2..c2d622e4d73 100644 --- a/src/test/java/seedu/address/model/activity/ActivityTest.java +++ b/src/test/java/seedu/address/model/activity/ActivityTest.java @@ -411,7 +411,15 @@ public void equals() { editedLunch = new ActivityBuilder(lunch).addPerson(TypicalPersons.ALICE).build(); assertFalse(lunch.equals(editedLunch)); - //TODO: Different expenses -> returns false; + // TODO: Different expenses -> returns false } + @Test + public void hashCode_sameActivityValues_hashCodeIsCorrect() { + Activity breakfast = TypicalActivities.BREAKFAST; + Activity breakfastCopy = new ActivityBuilder(breakfast).build(); + + // activities with same values -> returns true + assertEquals(breakfast.hashCode(), breakfastCopy.hashCode()); + } } diff --git a/src/test/java/seedu/address/stub/ModelStub.java b/src/test/java/seedu/address/stub/ModelStub.java index 705d6d96061..2dc3bdb02fa 100644 --- a/src/test/java/seedu/address/stub/ModelStub.java +++ b/src/test/java/seedu/address/stub/ModelStub.java @@ -2,6 +2,7 @@ import java.nio.file.Path; import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.function.Predicate; @@ -170,4 +171,14 @@ public ObservableList getFilteredActivityList() { public void updateFilteredActivityList(Predicate predicate) { throw new AssertionError("This method (updateFilteredActivityList) should not be called."); } + + @Override + public List getAssociatedPersons(Activity activity) { + throw new AssertionError("This method should not be called."); + } + + @Override + public List getAssociatedActivities(Person person) { + throw new AssertionError("This method should not be called."); + } } diff --git a/src/test/java/seedu/address/testutil/TypicalIndexes.java b/src/test/java/seedu/address/testutil/TypicalIndexes.java index 1e613937657..1e2f4732a59 100644 --- a/src/test/java/seedu/address/testutil/TypicalIndexes.java +++ b/src/test/java/seedu/address/testutil/TypicalIndexes.java @@ -6,7 +6,9 @@ * A utility class containing a list of {@code Index} objects to be used in tests. */ public class TypicalIndexes { - public static final Index INDEX_FIRST_PERSON = Index.fromOneBased(1); - public static final Index INDEX_SECOND_PERSON = Index.fromOneBased(2); - public static final Index INDEX_THIRD_PERSON = Index.fromOneBased(3); + public static final Index INDEX_FIRST = Index.fromOneBased(1); + public static final Index INDEX_SECOND = Index.fromOneBased(2); + public static final Index INDEX_THIRD = Index.fromOneBased(3); + public static final Index INDEX_FIFTH = Index.fromOneBased(5); + public static final Index INDEX_EIGHTH = Index.fromOneBased(8); }