diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 956d3aacfb1..c029c0c8dc4 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -254,6 +254,16 @@ Examples: * `findTag Finance` returns `John`, `Caroline` and `Bob` ![result for 'findTag Finance'](images/findTagFinance.png) + +### Inspecting a person : `inspect` + +Updates the inspect panel with the basic information and loan history of the person inspected. + +![help message](images/helpMessage.png) + +Format: `inspect or ` + + ### Editing loan of a person: `editLoan` Edits an existing club member's loan amount in the SectresBook. @@ -330,12 +340,12 @@ Format: `listNote` Edits an existing specified note in the SectresBook. -Format: `editNote INDEX [title/TITLE] [content/CONTENT] [tag/TAG]...` +Format: `editNote INDEX TITLE [title/TITLE] [content/CONTENT] [tag/TAG]...` Example of usage: * `editNote 1 content/Second club meeting` can be used to easily update the first note's contents. -* `editeNote 2 title/2020 alumni meeting` can be used to easily update the second note's title. +* `editNote alumni title/2020 alumni meeting` can be used to amend a note with the title "2020 alumni mtg", only if it is the only note containing "2021" in its title. ### Locating a note by title: `findNote` @@ -371,6 +381,17 @@ Format: `deleteNote INDEX` Examples: * `listNote` followed by `deleteNote 2` deletes the 2nd note in the SectresBook. +### Hiding notes panel : `hideNotes` + +Hides the notes panel to the right side of the screen if visible, otherwise, no operation is performed. + +Format: `hideNotes` + +### Showing notes panel : `showNotes` + +Slides the notes panel into view if hidden, otherwise, no operation is performed. + +Format: `showNotes` ### Exiting the program : `exit` @@ -435,25 +456,28 @@ Word | Definition ### AddressBook Commands -Action | Format | Examples ---------|-------------------------------------------------------------------------------------------------------|-------- -**Add** | `add name/NAME phone/PHONE_NUMBER email/EMAIL home/ADDRESS bday/BIRTHDAY [tag/TAG]…​` | `add name/James Ho phone/22224444 email/jamesho@example.com home/123, Clementi Rd, 1234665 bday/01/01/2000 tag/friend tag/colleague` -**Clear** | `clear` | `clear` -**Delete** | `delete INDEX`
`delete NAME` | `delete 3`
`delete Jane` +Action | Format | Examples +--------|------------------------------------------------------------------------------------------------------|-------- +**Add** | `add name/NAME phone/PHONE_NUMBER email/EMAIL home/ADDRESS bday/BIRTHDAY [tag/TAG]…​` | `add name/James Ho phone/22224444 email/jamesho@example.com home/123, Clementi Rd, 1234665 bday/01/01/2000 tag/friend tag/colleague` +**Clear** | `clear` | `clear` +**Delete** | `delete INDEX`
`delete NAME` | `delete 3`
`delete Jane` **Edit** | `edit INDEX [name/NAME] [phone/PHONE_NUMBER] [email/EMAIL] [home/ADDRESS] [bday/BIRTHDAY][tag/TAG]…​` | `edit 2 name/James Lee email/jameslee@example.com` -**Find** | `find KEYWORD [MORE_KEYWORDS]`
`find NUMBER` | `find James Jake`
`find 8651` -**Find Tag** | `findTag TAG [MORE_TAGS]` | `findTag Operations Outreach` -**Edit Loan** | `editLoan INDEX AMOUNT REASON` | `editLoan 1 -20 Buy Logistics` -**List** | `list` | `list` -**Help** | `help` | `help` -**Exit** | `exit` | `exit` +**Find** | `find KEYWORD [MORE_KEYWORDS]`
`find NUMBER` | `find James Jake`
`find 8651` +**Find Tag** | `findTag TAG [MORE_TAGS]` | `findTag Operations Outreach` +**Edit Loan** | `editLoan INDEX AMOUNT REASON` | `editLoan 1 -20 Buy Logistics` +**List** | `list` | `list` +**Inspect** | `inspect NAME INDEX` | `inspect 1` or `inspect Alex` +**Show Notes Panel** | `showNotes` | `showNotes` +**Hide Notes Panel** | `hideNotes` | `hideNotes` +**Help** | `help` | `help` +**Exit** | `exit` | `exit` ### Note Commands -Action | Format | Examples ---------|---------------------------|------------- -**Add Note** | `addNote title/TITLE content/CONTENT [tag/TAG]...` | `addNote title/Create Excel Sheet content/Create sheet for blockchain department` -**Edit Note** | `editNote INDEX [title/TITLE] [content/CONTENT] [tag/TAG]...` | `editNote 1 title/Check meeting availability tag/president` -**Delete Note** | `deleteNote INDEX` | `deleteNote 1` -**List Notes** | `listNote` | `listNote` -**Find Note** | `findNote KEYWORD [MORE_KEYWORDS]` | `findNote meeting` +Action | Format | Examples +--------|--------------------------------------------------------------------------|------------- +**Add Note** | `addNote title/TITLE content/CONTENT [tag/TAG]...` | `addNote title/Create Excel Sheet content/Create sheet for blockchain department` +**Edit Note** | `editNote INDEX TITLE [title/TITLE] [content/CONTENT] [tag/TAG]...` | `editNote 1 title/Check meeting availability tag/president` +**Delete Note** | `deleteNote INDEX` | `deleteNote 1` +**List Notes** | `listNote` | `listNote` +**Find Note** | `findNote KEYWORD [MORE_KEYWORDS]` | `findNote meeting` diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 704429a9108..b0d72cdc9c4 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -2,10 +2,12 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.Objects; import java.util.Optional; import java.util.logging.Logger; import javafx.application.Application; +import javafx.scene.text.Font; import javafx.stage.Stage; import seedu.address.commons.core.Config; import seedu.address.commons.core.LogsCenter; @@ -51,6 +53,8 @@ public void init() throws Exception { logger.info("=============================[ Initializing AddressBook ]==========================="); super.init(); + loadFonts(); + AppParameters appParameters = AppParameters.parse(getParameters()); config = initConfig(appParameters.getConfigPath()); @@ -68,6 +72,38 @@ public void init() throws Exception { ui = new UiManager(logic); } + private void loadFonts() { + Font.loadFont( + Objects.requireNonNull(MainApp.class.getResource("/fonts/MinionPro-Bold.otf")).toExternalForm(), + 10 + ); + + Font.loadFont( + Objects.requireNonNull(MainApp.class.getResource("/fonts/MinionPro-Medium.otf")).toExternalForm(), + 10 + ); + + Font.loadFont( + Objects.requireNonNull(MainApp.class.getResource("/fonts/MinionPro-Semibold.otf")).toExternalForm(), + 10 + ); + + Font.loadFont( + Objects.requireNonNull(MainApp.class.getResource("/fonts/Bender.otf")).toExternalForm(), + 10 + ); + + Font.loadFonts( + Objects.requireNonNull(MainApp.class.getResource("/fonts/Bender-Bold.otf")).toExternalForm(), + 10 + ); + + Font.loadFonts( + Objects.requireNonNull(MainApp.class.getResource("/fonts/Bender-Light.otf")).toExternalForm(), + 10 + ); + } + /** * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
* The data from the sample address book will be used instead if {@code storage}'s address book is not found, diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index ba89f8804a6..9e464f59b5a 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -1,19 +1,51 @@ package seedu.address.commons.core; +import seedu.address.logic.parser.CliSyntax; + /** * Container for user visible messages. */ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; + public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; + + public static final String MESSAGE_INVALID_AMBIGUOUS_TITLE = "There is more than 1 note with %s in their title!\n" + + "Please use a more unique specifier or use indices to edit."; + + public static final String MESSAGE_INVALID_TITLE = "There are no notes with %s in their titles in the list!"; + public static final String MESSAGE_INVALID_NOTE_DISPLAYED_INDEX = "The note index provided is invalid"; + public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; + public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_NOTES_LISTED_OVERVIEW = "%1$d notes listed!"; + public static final String MESSAGE_NUMBER_TOO_SHORT = "Number to check must be at least 2 digits"; + public static final String MESSAGE_INVALID_KEYWORD = "Keyword is invalid,keyword cannot contain special characters"; - public static final String MESSAGE_INVALID_AMBIGUOUS_NAME = "There is more than 1 person with %s in their name!"; - public static final String MESSAGE_INVALID_NAME = "There is no one with the name %s in the list!"; + public static final String MESSAGE_INVALID_AMBIGUOUS_NAME = "There is more than 1 person with %s in their name!\n" + + "Please use a more unique specifier or use indices to edit."; + + public static final String MESSAGE_INVALID_NAME = "There is no one with the name %s found!"; + + public static final String AMOUNT_NOT_SPECIFIED = "No amount to was specified to edit the loan with.\n" + + "Please use " + CliSyntax.PREFIX_LOAN_AMOUNT + " to specify a change in loan amount!"; + + public static final String REASON_NOT_SPECIFIED = "A reason must be given to change loan amounts.\n" + + "Please use " + CliSyntax.PREFIX_LOAN_REASON + " to specify a reason to change the loan value!"; + + public static final String OUT_OF_BOUNDS = "The index given to be inspected must be within " + + "the bounds of the list!"; + + public static final String NOT_AN_INTEGER = "The index given was not an integer value"; + + public static final String AMBIGUOUS_NAME_INSPECT_FIRST = "There was more than one person of that name found.\n" + + "Showing the first person matching the given name.\n" + + "Note that inspection works on the currently filtered list, " + + "perhaps you would like to filter away some persons first?"; } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 52b900f130c..fb9af957b3e 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -2,6 +2,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_BIRTHDAY; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_LOAN; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; @@ -25,6 +26,7 @@ public class AddCommand extends Command { + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_BIRTHDAY + "BIRTHDAY " + "[" + PREFIX_TAG + "TAG]..." + "[" + PREFIX_LOAN + "10]\n" + "Example: " + COMMAND_WORD + " " @@ -32,6 +34,7 @@ public class AddCommand extends Command { + PREFIX_PHONE + "98765432 " + PREFIX_EMAIL + "johnd@example.com " + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_BIRTHDAY + "28/10/2000 " + PREFIX_TAG + "friends " + PREFIX_LOAN + "50"; diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 92f900b7916..ead384b1829 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -2,6 +2,7 @@ import static java.util.Objects.requireNonNull; +import java.util.Arrays; import java.util.Objects; /** @@ -9,41 +10,51 @@ */ public class CommandResult { - private final String feedbackToUser; + /** + * Enum describing the state of the UI to trigger actions + */ + public enum UiState { + ShowHelp, + Exit, + Inspect, + HideNotes, ShowNotes, None + } + + private final UiState state; - /** Help information should be shown to the user. */ - private final boolean showHelp; + private final String[] args; - /** The application should exit. */ - private final boolean exit; + private final String feedbackToUser; /** - * Constructs a {@code CommandResult} with the specified fields. + * Constructs a {@code CommandResult} with the no additional state. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + public CommandResult(String feedbackToUser) { this.feedbackToUser = requireNonNull(feedbackToUser); - this.showHelp = showHelp; - this.exit = exit; + this.state = UiState.None; + this.args = new String[0]; } /** - * Constructs a {@code CommandResult} with the specified {@code feedbackToUser}, - * and other fields set to their default value. + * Constructs a {@code CommandResult} with the specified state. */ - public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); - } - - public String getFeedbackToUser() { - return feedbackToUser; + public CommandResult(String feedbackToUser, UiState state, String... args) { + this.feedbackToUser = requireNonNull(feedbackToUser); + this.state = state; + this.args = args; } - public boolean isShowHelp() { - return showHelp; + /** + * Constructs a {@code CommandResult} with the specified state. + */ + public CommandResult(String feedbackToUser, UiState state) { + this.feedbackToUser = requireNonNull(feedbackToUser); + this.state = state; + this.args = new String[0]; } - public boolean isExit() { - return exit; + public String getFeedbackToUser() { + return feedbackToUser; } @Override @@ -59,13 +70,20 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) - && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; + && getUiState() == otherCommandResult.getUiState() + && Arrays.equals(getArgs(), otherCommandResult.getArgs()); } @Override public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); + return Objects.hash(feedbackToUser, getUiState(), Arrays.hashCode(getArgs())); } + public UiState getUiState() { + return state; + } + + public String[] getArgs() { + return args; + } } diff --git a/src/main/java/seedu/address/logic/commands/EditLoanCommand.java b/src/main/java/seedu/address/logic/commands/EditLoanCommand.java index 2ebb202e449..8aaedcd61aa 100644 --- a/src/main/java/seedu/address/logic/commands/EditLoanCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditLoanCommand.java @@ -86,7 +86,10 @@ public CommandResult execute(Model model) throws CommandException { editedPerson.getTags().forEach(tag -> tag.addPerson(editedPerson)); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_LOAN_SUCCESS, editedPerson)); + int index = model.getFilteredPersonList().indexOf(editedPerson); + + return new CommandResult(String.format(MESSAGE_EDIT_LOAN_SUCCESS, editedPerson), + CommandResult.UiState.Inspect, String.valueOf(index + 1)); } @@ -99,13 +102,17 @@ private static Person createEditedPerson(Person personToEdit, EditLoanDescriptor Address updatedAddress = personToEdit.getAddress(); Birthday updatedBirthday = personToEdit.getBirthday(); Set updatedTags = personToEdit.getTags(); + double val = editLoanDescriptor.getLoan().get().getAmount() + personToEdit.getLoan().getAmount(); + Loan updatedLoan = new Loan(String.valueOf(val)); List updatedLoanHistory = new ArrayList<>(); + for (LoanHistory his : personToEdit.getHistory()) { updatedLoanHistory.add(his); } - updatedLoanHistory.add(editLoanDescriptor.getHistory().get()); + + editLoanDescriptor.getHistory().ifPresent(updatedLoanHistory::add); return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedBirthday, updatedTags, updatedLoan, updatedLoanHistory); diff --git a/src/main/java/seedu/address/logic/commands/EditNoteCommand.java b/src/main/java/seedu/address/logic/commands/EditNoteCommand.java index bb4137c4fb1..0d0f91eaddc 100644 --- a/src/main/java/seedu/address/logic/commands/EditNoteCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditNoteCommand.java @@ -29,9 +29,9 @@ public class EditNoteCommand extends Command { public static final String COMMAND_WORD = "editNote"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the Note identified " - + "by the index number used in the displayed note list. " + + "by the index number or the title used in the displayed note list. " + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer)" + + "Parameters: INDEX (must be a positive integer) TITLE (matches by substring) " + "[" + PREFIX_NOTES_TITLE + "TITLE] " + "[" + PREFIX_NOTES_CONTENT + "CONTENT] " + "[" + PREFIX_TAG + "TAG]...\n" diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..31999c2bc8d 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -13,7 +13,7 @@ public class ExitCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, CommandResult.UiState.Exit); } } diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..78ac4fcb260 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -16,6 +16,6 @@ public class HelpCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return new CommandResult(SHOWING_HELP_MESSAGE, CommandResult.UiState.ShowHelp); } } diff --git a/src/main/java/seedu/address/logic/commands/HideNotesPanelCommand.java b/src/main/java/seedu/address/logic/commands/HideNotesPanelCommand.java new file mode 100644 index 00000000000..852946dd119 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/HideNotesPanelCommand.java @@ -0,0 +1,21 @@ +package seedu.address.logic.commands; + +import seedu.address.model.Model; + +/** + * Format full help instructions for every command for display. + */ +public class HideNotesPanelCommand extends Command { + + public static final String COMMAND_WORD = "hideNotes"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Hides the notes panel.\n" + + "Example: " + COMMAND_WORD; + + public static final String HIDING_NOTES_PANEL_MESSAGE = "Hiding notes panel"; + + @Override + public CommandResult execute(Model model) { + return new CommandResult(HIDING_NOTES_PANEL_MESSAGE, CommandResult.UiState.HideNotes); + } +} diff --git a/src/main/java/seedu/address/logic/commands/InspectCommand.java b/src/main/java/seedu/address/logic/commands/InspectCommand.java new file mode 100644 index 00000000000..e6d503c4af8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/InspectCommand.java @@ -0,0 +1,41 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * Deletes a person identified using it's displayed index from the address book. + */ +public class InspectCommand extends Command { + + public static final String COMMAND_WORD = "inspect"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Inspects the person identified by the index number used or by name registered" + + " in the displayed person list.\n" + + "Parameters: INDEX (must be a positive integer) NAME\n" + + "Example: " + COMMAND_WORD + " Alex"; + + public static final String MESSAGE_INSPECT_PERSON = "Inspecting Person"; + + private final String[] splitName; + + public InspectCommand(String[] splitName) { + this.splitName = splitName; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + return new CommandResult(MESSAGE_INSPECT_PERSON, CommandResult.UiState.Inspect, splitName); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof InspectCommand // instanceof handles nulls + && splitName.equals(((InspectCommand) other).splitName)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/ShowNotesPanelCommand.java b/src/main/java/seedu/address/logic/commands/ShowNotesPanelCommand.java new file mode 100644 index 00000000000..7ada2c150a8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ShowNotesPanelCommand.java @@ -0,0 +1,21 @@ +package seedu.address.logic.commands; + +import seedu.address.model.Model; + +/** + * Format full help instructions for every command for display. + */ +public class ShowNotesPanelCommand extends Command { + + public static final String COMMAND_WORD = "showNotes"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows the notes panel.\n" + + "Example: " + COMMAND_WORD; + + public static final String SHOWING_NOTES_PANEL_MESSAGE = "Showing notes panel"; + + @Override + public CommandResult execute(Model model) { + return new CommandResult(SHOWING_NOTES_PANEL_MESSAGE, CommandResult.UiState.ShowNotes); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 53f074e17f1..fcb200d8bc6 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -25,7 +25,6 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.person.Reason; import seedu.address.model.tag.Tag; /** @@ -65,9 +64,9 @@ public AddCommand parse(String args) throws ParseException { Birthday birthday = ParserUtil.parseBirthday(argMultimap.getValue(PREFIX_BIRTHDAY).get()); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG), model); Loan loan = ParserUtil.parseLoan(argMultimap.getValue(PREFIX_LOAN).orElse("0")); + + // construct empty loan history List history = new ArrayList<>(); - LoanHistory init = new LoanHistory(loan, new Reason("initial")); - history.add(init); Person person = new Person(name, phone, email, address, birthday, tagList, loan, history); diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 73b2d23fefb..241fcbe46b2 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -20,8 +20,11 @@ import seedu.address.logic.commands.FindNoteCommand; import seedu.address.logic.commands.FindTagCommand; import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.HideNotesPanelCommand; +import seedu.address.logic.commands.InspectCommand; import seedu.address.logic.commands.ListCommand; import seedu.address.logic.commands.ListNoteCommand; +import seedu.address.logic.commands.ShowNotesPanelCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; @@ -79,6 +82,15 @@ public Command parseCommand(String userInput, Model model) throws ParseException case HelpCommand.COMMAND_WORD: return new HelpCommand(); + case InspectCommand.COMMAND_WORD: + return new InspectCommandParser(model).parse(arguments); + + case ShowNotesPanelCommand.COMMAND_WORD: + return new ShowNotesPanelCommand(); + + case HideNotesPanelCommand.COMMAND_WORD: + return new HideNotesPanelCommand(); + case AddNoteCommand.COMMAND_WORD: return new AddNoteCommandParser(model).parse(arguments); diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 9fe69c208e3..49daecfa2ba 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -3,6 +3,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_AMBIGUOUS_NAME; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_BIRTHDAY; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; @@ -11,10 +12,12 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import javafx.collections.ObservableList; import seedu.address.commons.core.index.Index; @@ -104,10 +107,14 @@ private void filterPersonListByName(String preamble, ParseException pe) throws P ObservableList filteredPersonList = model.getFilteredPersonList(); + String splitPreamble = Arrays.stream(preamble.split(" ")) + .map(x -> "\"" + x.trim() + "\"") + .collect(Collectors.joining(" or ")); + if (filteredPersonList.size() == 0) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); + throw new ParseException(String.format(MESSAGE_INVALID_NAME, splitPreamble), pe); } else if (filteredPersonList.size() > 1) { - throw new ParseException(String.format(MESSAGE_INVALID_AMBIGUOUS_NAME, preamble), pe); + throw new ParseException(String.format(MESSAGE_INVALID_AMBIGUOUS_NAME, splitPreamble), pe); } } diff --git a/src/main/java/seedu/address/logic/parser/EditLoanCommandParser.java b/src/main/java/seedu/address/logic/parser/EditLoanCommandParser.java index 1807c7ea170..e3702a7c912 100644 --- a/src/main/java/seedu/address/logic/parser/EditLoanCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditLoanCommandParser.java @@ -1,15 +1,24 @@ package seedu.address.logic.parser; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_AMBIGUOUS_NAME; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_LOAN_AMOUNT; import static seedu.address.logic.parser.CliSyntax.PREFIX_LOAN_REASON; +import java.util.Arrays; +import java.util.stream.Collectors; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.EditLoanCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.person.Loan; import seedu.address.model.person.LoanHistory; +import seedu.address.model.person.Person; import seedu.address.model.person.Reason; /** @@ -36,13 +45,22 @@ public EditLoanCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_LOAN_AMOUNT, PREFIX_LOAN_REASON); + Index index; String preamble = argMultimap.getPreamble(); - index = ParserUtil.parseIndex(preamble); + try { + index = ParserUtil.parseIndex(preamble); + } catch (ParseException pe) { + filterPersonListByName(preamble, pe); + index = Index.fromOneBased(1); + } + + Loan loanChange = ParserUtil.parseLoan(argMultimap.getValue(PREFIX_LOAN_AMOUNT) + .orElseThrow(() -> new ParseException(Messages.AMOUNT_NOT_SPECIFIED))); + Reason reasonChange = ParserUtil.parseReason(argMultimap.getValue(PREFIX_LOAN_REASON) + .orElseThrow(() -> new ParseException(Messages.REASON_NOT_SPECIFIED))); - Loan loanChange = ParserUtil.parseLoan(argMultimap.getValue(PREFIX_LOAN_AMOUNT).get()); - Reason reasonChange = ParserUtil.parseReason(argMultimap.getValue(PREFIX_LOAN_REASON).get()); LoanHistory toAdd = new LoanHistory(loanChange, reasonChange); EditLoanCommand.EditLoanDescriptor editLoanDescriptor = new EditLoanCommand.EditLoanDescriptor(); @@ -52,4 +70,31 @@ public EditLoanCommand parse(String args) throws ParseException { return new EditLoanCommand(index, editLoanDescriptor, toAdd); } + /** + * Filters the {@code ObservableList} by person name + * @param preamble the name to search for, by complete word + * @param pe the ParseException to throw on failure + * @throws ParseException if there is nobody found by the find command, or there exist + * an ambiguity + */ + private void filterPersonListByName(String preamble, ParseException pe) throws ParseException { + try { + new FindCommandParser().parse(preamble).execute(model); + } catch (ParseException ignored) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditLoanCommand.MESSAGE_USAGE), pe); + } + + ObservableList filteredPersonList = model.getFilteredPersonList(); + + String splitPreamble = Arrays.stream(preamble.split(" ")) + .map(x -> "\"" + x.trim() + "\"") + .collect(Collectors.joining(" or ")); + + if (filteredPersonList.size() == 0) { + throw new ParseException(String.format(MESSAGE_INVALID_NAME, splitPreamble), pe); + } else if (filteredPersonList.size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_AMBIGUOUS_NAME, splitPreamble), pe); + } + } + } diff --git a/src/main/java/seedu/address/logic/parser/EditNoteCommandParser.java b/src/main/java/seedu/address/logic/parser/EditNoteCommandParser.java index 200e4dd42a7..779425e28d6 100644 --- a/src/main/java/seedu/address/logic/parser/EditNoteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditNoteCommandParser.java @@ -1,21 +1,27 @@ package seedu.address.logic.parser; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_AMBIGUOUS_TITLE; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_TITLE; import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTES_CONTENT; import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTES_TITLE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; +import javafx.collections.ObservableList; import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.EditNoteCommand; import seedu.address.logic.commands.EditNoteCommand.EditNoteDescriptor; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; +import seedu.address.model.note.Note; import seedu.address.model.tag.Tag; /** @@ -43,12 +49,14 @@ public EditNoteCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NOTES_TITLE, PREFIX_NOTES_CONTENT, PREFIX_TAG); + String preamble = argMultimap.getPreamble(); Index index; try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); + index = ParserUtil.parseIndex(preamble); } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditNoteCommand.MESSAGE_USAGE), pe); + filterNotesByTitle(preamble, pe); + index = Index.fromOneBased(1); } EditNoteDescriptor editNoteDescriptor = new EditNoteDescriptor(); @@ -83,4 +91,31 @@ private Optional> parseTagsForEdit(Collection tags) throws Pars return Optional.of(ParserUtil.parseTags(tagSet, model)); } + /** + * Filters the {@code ObservableList} by title + * @param preamble the name to search for, by complete word + * @param pe the ParseException to throw on failure + * @throws ParseException if there is nobody found by the find command, or there exist + * an ambiguity + */ + private void filterNotesByTitle(String preamble, ParseException pe) throws ParseException { + try { + new FindNoteCommandParser().parse(preamble).execute(model); + } catch (ParseException ignored) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditNoteCommand.MESSAGE_USAGE), pe); + } + + ObservableList filteredNoteList = model.getFilteredNoteList(); + + String splitPreamble = Arrays.stream(preamble.split(" ")) + .map(x -> "\"" + x.trim() + "\"") + .collect(Collectors.joining(" or ")); + + if (filteredNoteList.size() == 0) { + throw new ParseException(String.format(MESSAGE_INVALID_TITLE, splitPreamble), pe); + } else if (filteredNoteList.size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_AMBIGUOUS_TITLE, splitPreamble), pe); + } + } + } diff --git a/src/main/java/seedu/address/logic/parser/InspectCommandParser.java b/src/main/java/seedu/address/logic/parser/InspectCommandParser.java new file mode 100644 index 00000000000..ee729646cbb --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/InspectCommandParser.java @@ -0,0 +1,41 @@ +package seedu.address.logic.parser; + +import java.util.Arrays; + +import seedu.address.logic.commands.InspectCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Model; + +/** + * Parses input arguments and creates a new DeleteCommand object + */ +public class InspectCommandParser implements Parser { + private final Model model; + + /** + * Constructs a {@code DeleteCommandParser} + * @param model the model of the current state + */ + public InspectCommandParser(Model model) { + this.model = model; + } + + /** + * Parses the given {@code String} of arguments in the context of the DeleteCommand + * and returns a DeleteCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public InspectCommand parse(String args) throws ParseException { + String[] namePart = args.split(" ", 2); + + if (namePart.length < 2) { + return new InspectCommand(new String[0]); + } + + String[] splitName = Arrays.stream(namePart[1].split(" ")) + .map(x -> x.trim().replaceAll(" +", " ")) + .filter(x -> !x.equals(" ")).toArray(String[]::new); + + return new InspectCommand(splitName); + } +} diff --git a/src/main/java/seedu/address/model/person/Loan.java b/src/main/java/seedu/address/model/person/Loan.java index f0a6b6660b5..d8e4cc42ff2 100644 --- a/src/main/java/seedu/address/model/person/Loan.java +++ b/src/main/java/seedu/address/model/person/Loan.java @@ -27,6 +27,16 @@ public Loan(String amountString) { amountOwed = Double.parseDouble(amountString); } + /** + * Constructs a {@code Loan}. + * + * @param amount A double value to signifies a new loan amount + */ + public Loan(double amount) { + checkArgument(isValidLoan(Double.toString(amount)), MESSAGE_CONSTRAINTS); + amountOwed = amount; + } + public static boolean isValidLoan(String test) { return test.matches(VALIDATION_REGEX); } @@ -61,6 +71,24 @@ public String toString() { } } + /** + * Returns a string version of the loan similar to a normal {@code toString()} call + * and appends the plus sign at the front for positive values + * @param withSign whether to print the sign + * @return a string version of the amount owed with sign if needed + */ + public String toString(boolean withSign) { + if (!withSign) { + return toString(); + } + + if (amountOwed < 0) { + return String.format("-$%.2f", -amountOwed); + } else { + return String.format("+$%.2f", amountOwed); + } + } + /** * Returns true if both loans have the same identity and data fields. * This defines the notion of equality between two persons. @@ -78,7 +106,6 @@ public boolean equals(Object other) { if (((Loan) other).amountOwed == amountOwed) { return ((Loan) other).amountOwed == amountOwed; } else { - System.out.printf("%f not equals to %f%n", ((Loan) other).amountOwed, amountOwed); return false; } } diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index 4f9a51d30da..d084f5002b8 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -2,13 +2,16 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.ListIterator; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import javafx.util.Pair; import seedu.address.model.tag.Tag; /** @@ -80,6 +83,28 @@ public List getHistory() { return history; } + /** + * Combines the {@code LoanHistory} with {@code Loan} to produce a tracked history of loans together + * with the newly updated loan value at that point + * @return an {@code ArrayList} contains a {@code Pair} with key of type {@code Loan} and + * value of type {@code LoanHistory} + */ + public List> getHistoryWithTotal() { + Loan previousAmount = loan; + ArrayList> totalHistoryPair = new ArrayList<>(); + + ListIterator historyIterator = history.listIterator(history.size()); + + while (historyIterator.hasPrevious()) { + LoanHistory loanHistory = historyIterator.previous(); + totalHistoryPair.add(new Pair<>(previousAmount, loanHistory)); + previousAmount = new Loan(previousAmount.getAmount() - loanHistory.getLoanChange().getAmount()); + } + + return totalHistoryPair; + } + + /** * Returns true if both persons have the same name. * This defines a weaker notion of equality between two persons. diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index d6b319af6f0..8b24e8f5224 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -2,11 +2,15 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.note.Content; +import seedu.address.model.note.Note; +import seedu.address.model.note.Title; import seedu.address.model.person.Address; import seedu.address.model.person.Birthday; import seedu.address.model.person.Email; @@ -15,6 +19,7 @@ import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Reason; import seedu.address.model.tag.Tag; /** @@ -23,24 +28,105 @@ public class SampleDataUtil { public static Person[] getSamplePersons() { return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), new Birthday("20/01/2003"), - getTagSet("friends"), new Loan("0"), new ArrayList()), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), new Birthday("18/06/1999"), - getTagSet("colleagues", "friends"), new Loan("50"), new ArrayList()), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), new Birthday("03/05/1976"), - getTagSet("neighbours"), new Loan("121"), new ArrayList()), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), new Birthday("11/04/1985"), - getTagSet("family"), new Loan("-33"), new ArrayList()), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), new Birthday("25/09/2010"), - getTagSet("classmates"), new Loan("-10"), new ArrayList()), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), new Birthday("09/07/2005"), - getTagSet("colleagues"), new Loan("2"), new ArrayList()) + new Person( + new Name("Lynette Chong"), + new Phone("91883124"), + new Email("lynette@example.com"), + new Address("Blk 2 Holland Avenue, #23-05"), + new Birthday("09/07/1995"), + getTagSet("president"), + new Loan(0), List.of( + new LoanHistory(new Loan(100), new Reason("Deposited $100")), + new LoanHistory(new Loan(-100), new Reason("Invoice #066-011445-305")) + ) + ), + + new Person( + new Name("Gerald Yap"), + new Phone("84558922"), + new Email("gerald_yap@example.com"), + new Address("Blk 432 Bukit Panjang Ring Rd, #12-40"), + new Birthday("10/11/2000"), + getTagSet("colleagues", "operations"), + new Loan(-426), List.of( + new LoanHistory(new Loan(8), new Reason("Buy shirt")), + new LoanHistory(new Loan(-100), new Reason("Invoice #122-10493-293")), + new LoanHistory(new Loan(-200), new Reason("Invoice #133-589313-211")), + new LoanHistory(new Loan(-184), new Reason("Invoice #158-970377-008")), + new LoanHistory(new Loan(50), new Reason("Pay back excess charge")) + ) + ), + + new Person( + new Name("Alex Yeoh"), + new Phone("87438807"), + new Email("alexyeoh@example.com"), + new Address("Blk 30 Geylang Street 29, #06-40"), + new Birthday("20/01/2003"), + getTagSet("friends", "operations"), + new Loan(0), new ArrayList()), + + new Person( + new Name("Bernice Yu"), + new Phone("99272758"), + new Email("berniceyu@example.com"), + new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), + new Birthday("18/06/1999"), + getTagSet("colleagues", "friends"), + new Loan(48), List.of( + new LoanHistory(new Loan(8), new Reason("Buy shirt")), + new LoanHistory(new Loan(40), new Reason("Loaned money to buy 2x white paint")) + ) + ), + + new Person( + new Name("Charlotte Oliveiro"), + new Phone("93210283"), + new Email("charlotte@example.com"), + new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), + new Birthday("03/05/1976"), + getTagSet("operations"), + new Loan(0), List.of( + new LoanHistory(new Loan(8), new Reason("Buy shirt")), + new LoanHistory(new Loan(42), new Reason("Loaned money to buy foam boards")), + new LoanHistory(new Loan(-80), new Reason("Invoice #210-399032-029")), + new LoanHistory(new Loan(30), new Reason("Paid back loaned amount")) + ) + ), + + new Person( + new Name("David Li"), + new Phone("91031282"), + new Email("lidavid@example.com"), + new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), + new Birthday("11/04/1985"), + getTagSet("family"), + new Loan(-42), List.of( + new LoanHistory(new Loan(8), new Reason("Buy shirt")), + new LoanHistory(new Loan(-50), new Reason("To pay back after fixing table")) + ) + ), + + new Person( + new Name("Irfan Ibrahim"), + new Phone("92492021"), + new Email("irfan@example.com"), + new Address("Blk 47 Tampines Street 20, #17-35"), + new Birthday("25/09/2010"), + getTagSet("classmates"), + new Loan(100), List.of( + new LoanHistory(new Loan(100), new Reason("Borrowed for mass dinner event funds")) + ) + ), + + new Person( + new Name("Roy Balakrishnan"), + new Phone("92624417"), + new Email("royb@example.com"), + new Address("Blk 45 Aljunied Street 85, #11-31"), + new Birthday("09/07/2005"), + getTagSet("colleagues"), + new Loan(0), new ArrayList()) }; } @@ -48,12 +134,32 @@ public static Tag[] getSampleTags() { return new Tag[] { new Tag("friends"), new Tag("colleagues"), - new Tag("neighbours"), + new Tag("operations"), new Tag("family"), new Tag("classmates") }; } + public static Note[] getSampleNotes() { + return new Note[] { + new Note( + new Title("Indent buffet for meeting"), + new Content("Buffet to be indented at 3pm next Tuesday"), + getTagSet("colleagues") + ), + new Note( + new Title("Collect funds from operations team"), + new Content("Collect $20 from everyone involved in operations"), + getTagSet("operations") + ), + new Note( + new Title("Check meeting availability"), + new Content("Prepare attendance roll for those coming for next Tuesday's meeting"), + getTagSet("president") + ) + }; + } + public static ReadOnlyAddressBook getSampleAddressBook() { AddressBook sampleAb = new AddressBook(); for (Person samplePerson : getSamplePersons()) { @@ -62,6 +168,9 @@ public static ReadOnlyAddressBook getSampleAddressBook() { for (Tag sampleTag : getSampleTags()) { sampleAb.addTag(sampleTag); } + for (Note sampleNote : getSampleNotes()) { + sampleAb.addNote(sampleNote); + } return sampleAb; } diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 9e75478664b..2c7d2f2a0c4 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -1,9 +1,14 @@ package seedu.address.ui; +import javafx.animation.FadeTransition; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.util.Duration; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; @@ -31,6 +36,26 @@ public CommandBox(CommandExecutor commandExecutor) { commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); } + /** + * Binds a result display below this command box + * @param resultDisplay the result display to bind + */ + public void bindResultsDisplay(StackPane resultDisplay) { + commandTextField.focusedProperty().addListener((obs, o, n) -> { + FadeTransition ft = new FadeTransition(Duration.millis(300), resultDisplay); + ft.setToValue(n ? 0.9 : 0); + ft.play(); + }); + + commandTextField.addEventHandler(KeyEvent.KEY_PRESSED, t -> { + if (t.getCode() == KeyCode.ESCAPE) { + resultDisplay.requestFocus(); + } + }); + + resultDisplay.requestFocus(); + } + /** * Handles the Enter button pressed event. */ diff --git a/src/main/java/seedu/address/ui/InspectionPanel.java b/src/main/java/seedu/address/ui/InspectionPanel.java new file mode 100644 index 00000000000..3d45a2a4d5f --- /dev/null +++ b/src/main/java/seedu/address/ui/InspectionPanel.java @@ -0,0 +1,159 @@ +package seedu.address.ui; + +import java.util.Objects; +import java.util.logging.Logger; + +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.util.Pair; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.person.Loan; +import seedu.address.model.person.LoanHistory; +import seedu.address.model.person.Person; + +/** + * Panel containing the list of persons. + */ +public class InspectionPanel extends UiPart { + private static final String PERSON_IMAGE_PATH = "/images/person.png"; + private static final String PHONE_IMAGE_PATH = "/images/phone.png"; + private static final String EMAIL_IMAGE_PATH = "/images/mail.png"; + private static final String ADDRESS_IMAGE_PATH = "/images/home.png"; + private static final String BIRTHDAY_IMAGE_PATH = "/images/birthday.png"; + private static final String LOAN_IMAGE_PATH = "/images/loan.png"; + private static final String NO_RECORDS_IMAGE_PATH = "/images/no_records.png"; + + private static final String FXML = "InspectionPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(InspectionPanel.class); + + @FXML + private ListView> historyListView; + + @FXML + private Label name; + @FXML + private ImageView nameImage; + + @FXML + private Label phone; + @FXML + private ImageView phoneImage; + + @FXML + private Label address; + @FXML + private ImageView addressImage; + + @FXML + private Label email; + @FXML + private ImageView emailImage; + + @FXML + private Label birthday; + @FXML + private ImageView birthdayImage; + + @FXML + private VBox basicInformation; + + @FXML + private ImageView loanIndicator; + + @FXML + private ImageView noRecordsImage; + + @FXML + private Label summaryText; + + + /** + * Creates a {@code PersonListPanel} with the given {@code ObservableList}. + */ + public InspectionPanel(ListView personListView) { + super(FXML); + setImageViews(); + personListView.getSelectionModel().selectedItemProperty() + .addListener((obs, o, n) -> setInspectParameters(n)); + + basicInformation.maxWidthProperty().bind(getRoot().widthProperty().multiply(0.33)); + basicInformation.prefWidthProperty().bind(basicInformation.maxWidthProperty()); + + personListView.getSelectionModel().select(0); + } + + private void setInspectParameters(Person n) { + String fullName = n.getName().fullName; + name.setText(fullName); + email.setText(n.getEmail().value); + phone.setText(n.getPhone().value); + address.setText(n.getAddress().value); + birthday.setText(n.getBirthday().value); + + double loanAmount = n.getLoan().getAmount(); + if (loanAmount >= 0) { + summaryText.setText(String.format("%s is due to pay $%.2f", fullName, loanAmount)); + } else { + summaryText.setText(String.format("%s is to be paid $%.2f", fullName, -loanAmount)); + } + + historyListView.setItems(FXCollections.observableList(n.getHistoryWithTotal())); + historyListView.setCellFactory(listView -> new HistoryListViewCell()); + + if (historyListView.getItems().size() == 0) { + noRecordsImage.setOpacity(1); + loanIndicator.setOpacity(0); + summaryText.setOpacity(0); + } else { + noRecordsImage.setOpacity(0); + loanIndicator.setOpacity(1); + summaryText.setOpacity(1); + } + } + + private void setImageViews() { + nameImage.setImage( + new Image(Objects.requireNonNull(getClass().getResourceAsStream(PERSON_IMAGE_PATH)))); + phoneImage.setImage( + new Image(Objects.requireNonNull(getClass().getResourceAsStream(PHONE_IMAGE_PATH)))); + emailImage.setImage( + new Image(Objects.requireNonNull(getClass().getResourceAsStream(EMAIL_IMAGE_PATH)))); + addressImage.setImage( + new Image(Objects.requireNonNull(getClass().getResourceAsStream(ADDRESS_IMAGE_PATH)))); + birthdayImage.setImage( + new Image(Objects.requireNonNull(getClass().getResourceAsStream(BIRTHDAY_IMAGE_PATH)))); + loanIndicator.setImage( + new Image(Objects.requireNonNull(getClass().getResourceAsStream(LOAN_IMAGE_PATH)))); + noRecordsImage.setImage( + new Image(Objects.requireNonNull(getClass().getResourceAsStream(NO_RECORDS_IMAGE_PATH)))); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code LoanHistory} using a {@code LoanHistory}. + */ + class HistoryListViewCell extends ListCell> { + @Override + protected void updateItem(Pair totalAndHistory, boolean empty) { + super.updateItem(totalAndHistory, empty); + + if (empty || totalAndHistory == null) { + setGraphic(null); + setText(null); + } else { + LoanHistoryCard loanHistoryCard = new LoanHistoryCard(totalAndHistory.getValue(), + totalAndHistory.getKey().toString()); + + loanHistoryCard.bindWidth(historyListView, 15); + setGraphic(loanHistoryCard.getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/LoanHistoryCard.java b/src/main/java/seedu/address/ui/LoanHistoryCard.java new file mode 100644 index 00000000000..5479ccd0c41 --- /dev/null +++ b/src/main/java/seedu/address/ui/LoanHistoryCard.java @@ -0,0 +1,90 @@ +package seedu.address.ui; + +import java.util.Objects; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.person.LoanHistory; + + +/** + * An UI component that displays information of a {@code Person}. + */ +public class LoanHistoryCard extends UiPart { + + private static final String FXML = "LoanHistoryCard.fxml"; + + private static final String INCREASE_IMAGE_PATH = "/images/increase_arrow.png"; + private static final String DECREASE_IMAGE_PATH = "/images/decrease_arrow.png"; + + /** + * 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 LoanHistory history; + + @FXML + private HBox cardPane; + @FXML + private ImageView changeIdentifierImage; + @FXML + private Label currentLoanAmount; + @FXML + private Label changeInAmount; + @FXML + private Label reason; + + /** + * Creates a {@code PersonCode} with the given {@code Person} and index to display. + */ + public LoanHistoryCard(LoanHistory history, String updatedAmountString) { + super(FXML); + this.history = history; + currentLoanAmount.setText(updatedAmountString); + + changeIdentifierImage.setImage(new Image(Objects.requireNonNull(getClass() + .getResourceAsStream(history.getLoanChange().getAmount() > 0 + ? INCREASE_IMAGE_PATH + : DECREASE_IMAGE_PATH)) + )); + + changeInAmount.setText(this.history.getLoanChange().toString(true)); + + reason.setText(this.history.getReason().toString()); + } + + /** + * Binds the width of the contents of this card to the width of the list view + * @param listView the list view to bind to + * @param padding the padding applied after the width property + */ + public void bindWidth(ListView listView, double padding) { + getRoot().maxWidthProperty().bind(listView.widthProperty().subtract(padding)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof LoanHistoryCard)) { + return false; + } + + // state check + LoanHistoryCard card = (LoanHistoryCard) other; + return history.equals(card.history); + } +} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..f9992dc0787 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -4,25 +4,30 @@ import javafx.event.ActionEvent; import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.scene.control.ListView; import javafx.scene.control.MenuItem; import javafx.scene.control.TextInputControl; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; +import javafx.scene.layout.AnchorPane; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.util.StringUtil; import seedu.address.logic.Logic; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Person; /** * The Main Window. Provides the basic application layout containing * a menu bar and space where other JavaFX elements can be placed. */ public class MainWindow extends UiPart { - private static final String FXML = "MainWindow.fxml"; private final Logger logger = LogsCenter.getLogger(getClass()); @@ -31,7 +36,7 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; + private WindowAnchorPane windowAnchorPane; private ResultDisplay resultDisplay; private HelpWindow helpWindow; @@ -41,21 +46,25 @@ public class MainWindow extends UiPart { @FXML private MenuItem helpMenuItem; - @FXML - private StackPane personListPanelPlaceholder; - @FXML private StackPane resultDisplayPlaceholder; @FXML private StackPane statusbarPlaceholder; + @FXML + private AnchorPane windowAnchorPaneHolder; + + private final Scene scene; + /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ public MainWindow(Stage primaryStage, Logic logic) { super(FXML, primaryStage); + scene = primaryStage.getScene(); + // Set dependencies this.primaryStage = primaryStage; this.logic = logic; @@ -63,9 +72,12 @@ public MainWindow(Stage primaryStage, Logic logic) { // Configure the UI setWindowDefaultSize(logic.getGuiSettings()); - setAccelerators(); + // No more menu bar + //setAccelerators(); helpWindow = new HelpWindow(); + + primaryStage.getScene().setOnMouseClicked(e -> resultDisplayPlaceholder.requestFocus()); } public Stage getPrimaryStage() { @@ -110,17 +122,34 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + windowAnchorPane = new WindowAnchorPane(logic); + windowAnchorPane.fillInnerParts(); + windowAnchorPaneHolder.getChildren().add(windowAnchorPane.getRoot()); + windowAnchorPane.resizeElements(primaryStage.getHeight(), + primaryStage.getWidth()); resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); - statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + // we removed the status bar + // StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); + // statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(this::executeCommand); + commandBox.bindResultsDisplay(resultDisplayPlaceholder); + commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + resultDisplayPlaceholder.prefWidthProperty().bind(scene.widthProperty()); + + scene.heightProperty().addListener((ob, oldVal, newVal) -> + windowAnchorPane.resizeElements(scene.getHeight(), + scene.getWidth()) + ); + scene.widthProperty().addListener((ob, oldVal, newVal) -> + windowAnchorPane.resizeElements(scene.getHeight(), scene.getWidth()) + ); + + windowAnchorPane.resizeElements(scene.getHeight(), scene.getWidth()); } /** @@ -163,8 +192,65 @@ private void handleExit() { primaryStage.hide(); } + private void handleInspect(String[] inspectingName) { + if (inspectingName == null || inspectingName.length == 0) { + resultDisplay.setFeedbackToUser("There was nothing given to inspect"); + return; + } + + ListView personListView = getPersonListPanel().getListView(); + + try { + int index = Integer.parseInt(inspectingName[0]) - 1; + if (index < 0 || index >= personListView.getItems().size()) { + resultDisplay.setFeedbackToUser(Messages.OUT_OF_BOUNDS); + return; + } + + personListView.getSelectionModel().clearSelection(); + personListView.getSelectionModel().select(index); + return; + } catch (NumberFormatException e) { + logger.info(Messages.NOT_AN_INTEGER); + } + + Person[] personsArray = personListView.getItems() + .stream().filter(x -> matches(x.getName().fullName, inspectingName)) + .toArray(Person[]::new); + + if (personsArray.length == 0) { + resultDisplay.setFeedbackToUser(Messages.MESSAGE_INVALID_NAME); + return; + } + + if (personsArray.length > 1) { + resultDisplay.setFeedbackToUser(Messages.AMBIGUOUS_NAME_INSPECT_FIRST); + } + + int index = personListView.getItems().indexOf(personsArray[0]); + personListView.getSelectionModel().clearSelection(); + personListView.getSelectionModel().select(index); + } + + private void handleShowNotePanel(boolean isVisible) { + windowAnchorPane.setNotesPaneVisibility(isVisible, scene.getHeight(), scene.getWidth()); + } + + private boolean matches(String currentName, String[] matching) { + for (String word : matching) { + if (StringUtil.containsWordIgnoreCase(currentName, word)) { + return true; + } + } + return false; + } + public PersonListPanel getPersonListPanel() { - return personListPanel; + return windowAnchorPane.getPersonListPanel(); + } + + public NoteListPanel getNoteListPanel() { + return windowAnchorPane.getNoteListPanel(); } /** @@ -178,14 +264,37 @@ private CommandResult executeCommand(String commandText) throws CommandException logger.info("Result: " + commandResult.getFeedbackToUser()); resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); - if (commandResult.isShowHelp()) { + switch (commandResult.getUiState()) { + case ShowHelp: handleHelp(); - } + break; - if (commandResult.isExit()) { + case Exit: handleExit(); + break; + + case Inspect: + handleInspect(commandResult.getArgs()); + break; + + case HideNotes: + handleShowNotePanel(false); + break; + + case ShowNotes: + handleShowNotePanel(true); + break; + + default: + break; } + getPersonListPanel().setFilteredBoxIcon( + logic.getAddressBook().getPersonList().size() != logic.getFilteredPersonList().size()); + + getNoteListPanel().setFilteredBoxIcon( + logic.getAddressBook().getNoteBook().size() != logic.getFilteredNoteList().size()); + return commandResult; } catch (CommandException | ParseException e) { logger.info("Invalid command: " + commandText); diff --git a/src/main/java/seedu/address/ui/NoteCard.java b/src/main/java/seedu/address/ui/NoteCard.java new file mode 100644 index 00000000000..6bf3408951c --- /dev/null +++ b/src/main/java/seedu/address/ui/NoteCard.java @@ -0,0 +1,81 @@ +package seedu.address.ui; + +import java.util.Comparator; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.note.Note; + +/** + * An UI component that displays information of a {@code Person}. + */ +public class NoteCard extends UiPart { + + private static final String FXML = "NoteListCard.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 Note note; + + @FXML + private HBox cardPane; + @FXML + private Label title; + @FXML + private Label id; + @FXML + private Label content; + @FXML + private FlowPane tags; + + /** + * Creates a {@code PersonCode} with the given {@code Person} and index to display. + */ + public NoteCard(Note note, int displayedIndex) { + super(FXML); + this.note = note; + id.setText(displayedIndex + ". "); + title.setText(note.getTitle().fullTitle); + content.setText(note.getContent().fullContent); + note.getTags().stream() + .sorted(Comparator.comparing(tag -> tag.tagName)) + .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + } + + /** + * Binds the width of the contents of this card to the width of the list view + * @param listView the list view to bind to + * @param padding the padding applied after the width property + */ + public void bindWidth(ListView listView, double padding) { + getRoot().maxWidthProperty().bind(listView.widthProperty().subtract(padding)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof NoteCard)) { + return false; + } + + // state check + NoteCard card = (NoteCard) other; + return id.getText().equals(card.id.getText()) + && note.equals(card.note); + } +} diff --git a/src/main/java/seedu/address/ui/NoteListPanel.java b/src/main/java/seedu/address/ui/NoteListPanel.java new file mode 100644 index 00000000000..8398a41b471 --- /dev/null +++ b/src/main/java/seedu/address/ui/NoteListPanel.java @@ -0,0 +1,81 @@ +package seedu.address.ui; + +import java.util.Objects; +import java.util.logging.Logger; + +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.util.Duration; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.note.Note; + +/** + * Panel containing the list of persons. + */ +public class NoteListPanel extends UiPart { + private static final String FXML = "NoteListPanel.fxml"; + private static final String FILTER_IMAGE_PATH = "/images/filter.png"; + private final Logger logger = LogsCenter.getLogger(NoteListPanel.class); + + @FXML + private ListView noteListView; + @FXML + private ImageView notebookLogo; + + @FXML + private HBox filteredBox; + + @FXML + private ImageView filteredImage; + + /** + * Creates a {@code NoteListPanel} with the given {@code ObservableList}. + */ + public NoteListPanel(ObservableList noteList) { + super(FXML); + noteListView.setItems(noteList); + noteListView.setCellFactory(listView -> new NoteListViewCell()); + notebookLogo.setImage( + new Image(Objects.requireNonNull(getClass().getResource("/images/notebook.png")) + .toString())); + filteredImage.setImage( + new Image(Objects.requireNonNull(getClass().getResourceAsStream(FILTER_IMAGE_PATH)))); + filteredBox.setOpacity(0); + } + + public void setFilteredBoxIcon(boolean isVisible) { + Timeline timeline = new Timeline(); + timeline.getKeyFrames().add(new KeyFrame(Duration.millis(500), + new KeyValue(filteredBox.opacityProperty(), isVisible ? 1 : 0))); + timeline.play(); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Note} using a {@code NoteCard}. + */ + class NoteListViewCell extends ListCell { + @Override + protected void updateItem(Note note, boolean empty) { + super.updateItem(note, empty); + + if (empty || note == null) { + setGraphic(null); + setText(null); + } else { + NoteCard noteCard = new NoteCard(note, getIndex() + 1); + noteCard.bindWidth(noteListView, 20); + setGraphic(noteCard.getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 8d251b0e131..0961b54c1b6 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -1,9 +1,12 @@ package seedu.address.ui; import java.util.Comparator; +import java.util.Objects; import javafx.fxml.FXML; import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; @@ -13,6 +16,8 @@ * An UI component that displays information of a {@code Person}. */ public class PersonCard extends UiPart { + private static final String PHONE_IMAGE_PATH = "/images/phone.png"; + private static final String LOAN_IMAGE_PATH = "/images/loan.png"; private static final String FXML = "PersonListCard.fxml"; @@ -35,9 +40,11 @@ public class PersonCard extends UiPart { @FXML private Label phone; @FXML - private Label address; + private ImageView phoneImage; @FXML - private Label email; + private Label loanAmount; + @FXML + private ImageView loanImage; @FXML private Label birthday; @FXML @@ -49,12 +56,14 @@ public class PersonCard extends UiPart { public PersonCard(Person person, int displayedIndex) { super(FXML); this.person = person; - id.setText(displayedIndex + ". "); + id.setText("#" + displayedIndex); name.setText(person.getName().fullName); + phoneImage.setImage( + new Image(Objects.requireNonNull(getClass().getResourceAsStream(PHONE_IMAGE_PATH)))); phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - birthday.setText(person.getBirthday().value); + loanImage.setImage( + new Image(Objects.requireNonNull(getClass().getResourceAsStream(LOAN_IMAGE_PATH)))); + loanAmount.setText(person.getLoan().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/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java index f4c501a897b..27ad03c9694 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/address/ui/PersonListPanel.java @@ -1,12 +1,20 @@ package seedu.address.ui; +import java.util.Objects; import java.util.logging.Logger; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; import javafx.scene.layout.Region; +import javafx.util.Duration; import seedu.address.commons.core.LogsCenter; import seedu.address.model.person.Person; @@ -15,11 +23,17 @@ */ public class PersonListPanel extends UiPart { private static final String FXML = "PersonListPanel.fxml"; + private static final String FILTER_IMAGE_PATH = "/images/filter.png"; private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); @FXML private ListView personListView; + @FXML + private ImageView filteredImage; + @FXML + private HBox filteredBox; + /** * Creates a {@code PersonListPanel} with the given {@code ObservableList}. */ @@ -27,12 +41,27 @@ public PersonListPanel(ObservableList personList) { super(FXML); personListView.setItems(personList); personListView.setCellFactory(listView -> new PersonListViewCell()); + filteredImage.setImage( + new Image(Objects.requireNonNull(getClass().getResourceAsStream(FILTER_IMAGE_PATH)))); + filteredBox.setOpacity(0); + } + + public void setFilteredBoxIcon(boolean isVisible) { + Timeline timeline = new Timeline(); + timeline.getKeyFrames().add(new KeyFrame(Duration.millis(500), + new KeyValue(filteredBox.opacityProperty(), isVisible ? 1 : 0))); + timeline.play(); + } + + public ListView getListView() { + return personListView; } /** * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. */ class PersonListViewCell extends ListCell { + @Override protected void updateItem(Person person, boolean empty) { super.updateItem(person, empty); @@ -41,9 +70,20 @@ protected void updateItem(Person person, boolean empty) { setGraphic(null); setText(null); } else { + if (personListView.getSelectionModel().getSelectedIndex() != getIndex()) { + setOpacity(0.7); + } setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); } + + focusedProperty().addListener((ob, o, n) -> setOpacity(1)); + + setOnMouseEntered(e -> setOpacity(1)); + setOnMouseExited(e -> { + if (personListView.getSelectionModel().getSelectedIndex() != getIndex()) { + setOpacity(0.7); + } + }); } } - } diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/address/ui/ResultDisplay.java index 7d98e84eedf..158cd9b78b5 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/seedu/address/ui/ResultDisplay.java @@ -16,6 +16,9 @@ public class ResultDisplay extends UiPart { @FXML private TextArea resultDisplay; + /** + * Displays the results of the command to the user + */ public ResultDisplay() { super(FXML); } diff --git a/src/main/java/seedu/address/ui/WindowAnchorPane.java b/src/main/java/seedu/address/ui/WindowAnchorPane.java new file mode 100644 index 00000000000..d3043a8e2a8 --- /dev/null +++ b/src/main/java/seedu/address/ui/WindowAnchorPane.java @@ -0,0 +1,163 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.value.WritableDoubleValue; +import javafx.fxml.FXML; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.util.Duration; +import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.Logic; + +/** + * Panel containing the list of persons. + */ +public class WindowAnchorPane extends UiPart { + private static final double HORIZONTAL_DIVIDER = 0.45; + private static final double DISTANCE_BOTTOM = 0; + private static final double VERTICAL_DIVIDER_DEFAULT = 0.65; + + private static final String FXML = "WindowAnchorPane.fxml"; + private static final double OFFSET_HEIGHT = 60; + private static final double RIGHT_PADDING = 10; + private static final double OUT_OF_BOUNDS_RIGHT = -400; + private final Logger logger = LogsCenter.getLogger(WindowAnchorPane.class); + + private double verticalDivider = VERTICAL_DIVIDER_DEFAULT; + + private Logic logic; + private PersonListPanel personListPanel; + private NoteListPanel noteListPanel; + private InspectionPanel inspectionPanel; + + @FXML + private AnchorPane container; + + @FXML + private StackPane personListPanelPlaceholder; + + @FXML + private StackPane noteListPanelPlaceholder; + + @FXML + private StackPane inspectionPanelPlaceholder; + + @FXML + private VBox personList; + + @FXML + private VBox noteList; + + @FXML + private VBox inspectionSection; + + /** + * Creates a {@code PersonListPanel} with the given {@code ObservableList}. + */ + public WindowAnchorPane(Logic logic) { + super(FXML); + this.logic = logic; + } + + /** + * Fills up all the placeholders of this window. + */ + void fillInnerParts() { + personListPanel = new PersonListPanel(logic.getFilteredPersonList()); + personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + + inspectionPanel = new InspectionPanel(personListPanel.getListView()); + inspectionPanelPlaceholder.getChildren().add(inspectionPanel.getRoot()); + + noteListPanel = new NoteListPanel(logic.getFilteredNoteList()); + noteListPanelPlaceholder.getChildren().add(noteListPanel.getRoot()); + } + + /** + * Resizes the children elements inside the AnchorPane + * @param stageHeight the height of the stage this anchor pane is one + * @param stageWidth the width of the stage this anchor pane is one + */ + public void resizeElements(double stageHeight, double stageWidth) { + //Confine anchor pane container to width of stage + stageHeight -= OFFSET_HEIGHT; + + container.setMaxWidth(stageWidth); + container.setMaxHeight(stageHeight); + container.setPrefWidth(stageWidth); + container.setPrefHeight(stageHeight); + + //set anchors of personListPanel + AnchorPane.setLeftAnchor(personList, 0.0); + AnchorPane.setRightAnchor(personList, (1.0 - verticalDivider) * stageWidth); + AnchorPane.setTopAnchor(personList, 0.0); + AnchorPane.setBottomAnchor(personList, (1 - HORIZONTAL_DIVIDER) * stageHeight); + + //set anchors of inspectPanel + AnchorPane.setLeftAnchor(inspectionSection, 0.0); + AnchorPane.setRightAnchor(inspectionSection, (1.0 - verticalDivider) * stageWidth); + AnchorPane.setTopAnchor(inspectionSection, HORIZONTAL_DIVIDER * stageHeight); + AnchorPane.setBottomAnchor(inspectionSection, DISTANCE_BOTTOM); + + + //set anchors of noteListPanel + AnchorPane.setLeftAnchor(noteList, (verticalDivider) * stageWidth); + AnchorPane.setRightAnchor(noteList, stageWidth * (VERTICAL_DIVIDER_DEFAULT - verticalDivider)); + AnchorPane.setTopAnchor(noteList, 10.0); + AnchorPane.setBottomAnchor(noteList, DISTANCE_BOTTOM); + + container.autosize(); + } + + public PersonListPanel getPersonListPanel() { + return personListPanel; + } + + public NoteListPanel getNoteListPanel() { + return noteListPanel; + } + + public void setNotesPaneVisibility(boolean isVisible, double stageHeight, double stageWidth) { + Timeline anim = new Timeline(); + anim.getKeyFrames().add(new KeyFrame(Duration.millis(300), new KeyValue( + new WritableDoubleValue() { + @Override + public double get() { + return WindowAnchorPane.this.verticalDivider; + } + + @Override + public void set(double value) { + verticalDivider = value; + resizeElements(stageHeight, stageWidth); + } + + @Override + public void setValue(Number value) { + verticalDivider = (double) value; + resizeElements(stageHeight, stageWidth); + } + + @Override + public Number getValue() { + return WindowAnchorPane.this.verticalDivider; + } + }, + isVisible ? VERTICAL_DIVIDER_DEFAULT : 1, + Interpolator.LINEAR))); + + anim.getKeyFrames().add(new KeyFrame(Duration.millis(300), new KeyValue( + noteList.opacityProperty(), + isVisible ? 1 : 0, + Interpolator.LINEAR))); + + anim.play(); + } +} diff --git a/src/main/resources/fonts/Bender-Black.otf b/src/main/resources/fonts/Bender-Black.otf new file mode 100644 index 00000000000..7e4b1d592fa Binary files /dev/null and b/src/main/resources/fonts/Bender-Black.otf differ diff --git a/src/main/resources/fonts/Bender-Bold.otf b/src/main/resources/fonts/Bender-Bold.otf new file mode 100644 index 00000000000..204e9e619cc Binary files /dev/null and b/src/main/resources/fonts/Bender-Bold.otf differ diff --git a/src/main/resources/fonts/Bender-Light.otf b/src/main/resources/fonts/Bender-Light.otf new file mode 100644 index 00000000000..bc0cadafcad Binary files /dev/null and b/src/main/resources/fonts/Bender-Light.otf differ diff --git a/src/main/resources/fonts/Bender.otf b/src/main/resources/fonts/Bender.otf new file mode 100644 index 00000000000..e2ae7ee4936 Binary files /dev/null and b/src/main/resources/fonts/Bender.otf differ diff --git a/src/main/resources/fonts/MinionPro-Bold.otf b/src/main/resources/fonts/MinionPro-Bold.otf new file mode 100644 index 00000000000..1dc230fa8b1 Binary files /dev/null and b/src/main/resources/fonts/MinionPro-Bold.otf differ diff --git a/src/main/resources/fonts/MinionPro-Medium.otf b/src/main/resources/fonts/MinionPro-Medium.otf new file mode 100644 index 00000000000..76a2c181412 Binary files /dev/null and b/src/main/resources/fonts/MinionPro-Medium.otf differ diff --git a/src/main/resources/fonts/MinionPro-Semibold.otf b/src/main/resources/fonts/MinionPro-Semibold.otf new file mode 100644 index 00000000000..22525aef3eb Binary files /dev/null and b/src/main/resources/fonts/MinionPro-Semibold.otf differ diff --git a/src/main/resources/images/bg.png b/src/main/resources/images/bg.png new file mode 100644 index 00000000000..4e5cec32c27 Binary files /dev/null and b/src/main/resources/images/bg.png differ diff --git a/src/main/resources/images/birthday.png b/src/main/resources/images/birthday.png new file mode 100644 index 00000000000..f786cd0a1ea Binary files /dev/null and b/src/main/resources/images/birthday.png differ diff --git a/src/main/resources/images/decrease_arrow.png b/src/main/resources/images/decrease_arrow.png new file mode 100644 index 00000000000..ef9f53d5278 Binary files /dev/null and b/src/main/resources/images/decrease_arrow.png differ diff --git a/src/main/resources/images/filter.png b/src/main/resources/images/filter.png new file mode 100644 index 00000000000..19c7571e6d8 Binary files /dev/null and b/src/main/resources/images/filter.png differ diff --git a/src/main/resources/images/home.png b/src/main/resources/images/home.png new file mode 100644 index 00000000000..d10e62fd062 Binary files /dev/null and b/src/main/resources/images/home.png differ diff --git a/src/main/resources/images/increase_arrow.png b/src/main/resources/images/increase_arrow.png new file mode 100644 index 00000000000..fb7739be0c1 Binary files /dev/null and b/src/main/resources/images/increase_arrow.png differ diff --git a/src/main/resources/images/indicator_arrow.png b/src/main/resources/images/indicator_arrow.png new file mode 100644 index 00000000000..8d81b2d072b Binary files /dev/null and b/src/main/resources/images/indicator_arrow.png differ diff --git a/src/main/resources/images/loan.png b/src/main/resources/images/loan.png new file mode 100644 index 00000000000..ee5f0b64545 Binary files /dev/null and b/src/main/resources/images/loan.png differ diff --git a/src/main/resources/images/mail.png b/src/main/resources/images/mail.png new file mode 100644 index 00000000000..2bf0cda7f91 Binary files /dev/null and b/src/main/resources/images/mail.png differ diff --git a/src/main/resources/images/no_records.png b/src/main/resources/images/no_records.png new file mode 100644 index 00000000000..9baa2f74743 Binary files /dev/null and b/src/main/resources/images/no_records.png differ diff --git a/src/main/resources/images/notebook.png b/src/main/resources/images/notebook.png new file mode 100644 index 00000000000..fe5a42f0c17 Binary files /dev/null and b/src/main/resources/images/notebook.png differ diff --git a/src/main/resources/images/person.png b/src/main/resources/images/person.png new file mode 100644 index 00000000000..9a2d239b4d6 Binary files /dev/null and b/src/main/resources/images/person.png differ diff --git a/src/main/resources/images/phone.png b/src/main/resources/images/phone.png new file mode 100644 index 00000000000..b61c9b3014f Binary files /dev/null and b/src/main/resources/images/phone.png differ diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 09f6d6fe9e4..ef2166d8fa0 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -3,7 +3,7 @@ - + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..b5478545518 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -1,32 +1,44 @@ +* { + -fx-bg-color: Gainsboro; + -fx-contrast-color: #F2D492; + -fx-highlight-color: #B8B08D; + -fx-shadow-color: #2C3333; + -fx-midtone-color: #544464; + -fx-darktone-color: #29153D; +} + .background { - -fx-background-color: derive(#1d1d1d, 20%); - background-color: #383838; /* Used in the default.html file */ + -fx-background-color: radial-gradient(focus-distance 0% , center 50% 50% , radius 55% , AliceBlue, -fx-bg-color); + background-color: -fx-bg-color; /* Used in the default.html file */ + -fx-background-image: url("/images/bg.png"); + -fx-background-size: cover; } .label { -fx-font-size: 11pt; - -fx-font-family: "Segoe UI Semibold"; + -fx-font-family: 'MinionPro-Semibold'; -fx-text-fill: #555555; -fx-opacity: 0.9; } .label-bright { -fx-font-size: 11pt; - -fx-font-family: "Segoe UI Semibold"; + -fx-font-family: 'MinionPro-Semibold'; -fx-text-fill: white; -fx-opacity: 1; } .label-header { - -fx-font-size: 32pt; - -fx-font-family: "Segoe UI Light"; + -fx-font-size: 21pt; + -fx-font-family: 'Bender-Bold'; -fx-text-fill: white; -fx-opacity: 1; } .text-field { -fx-font-size: 12pt; - -fx-font-family: "Segoe UI Semibold"; + -fx-background-color: -fx-bg-color; + -fx-font-family: 'MinionPro-Semibold' } .tab-pane { @@ -40,9 +52,9 @@ } .table-view { - -fx-base: #1d1d1d; - -fx-control-inner-background: #1d1d1d; - -fx-background-color: #1d1d1d; + -fx-base: -fx-bg-color; + -fx-control-inner-background: -fx-bg-color; + -fx-background-color: -fx-bg-color; -fx-table-cell-border-color: transparent; -fx-table-header-border-color: transparent; -fx-padding: 5; @@ -66,7 +78,7 @@ .table-view .column-header .label { -fx-font-size: 20pt; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: 'Bender-Bold Regular'; -fx-text-fill: white; -fx-alignment: center-left; -fx-opacity: 1; @@ -77,58 +89,144 @@ } .split-pane:horizontal .split-pane-divider { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(-fx-bg-color, 20%); -fx-border-color: transparent transparent transparent #4d4d4d; } .split-pane { -fx-border-radius: 1; -fx-border-width: 1; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(-fx-bg-color, 20%); +} + +.notes-VBox { + -fx-background-color: rgba(0, 0, 0, 0.4); + -fx-background-radius: 0; + -fx-padding: 5, 5, 5, 5; + -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.3), 10, 0.5, -5.0, 0.0); +} + +.person-VBox { + -fx-background-color: transparent; + -fx-background-radius: 0; +} + +.filteredBox { + -fx-background-color: white; +} + +.filteredBox .label { + -fx-font-family: 'Bender-Bold'; +} + +.label#invertedScheme { + -fx-background-color: black; + -fx-text-fill: white; +} + +.label#normalScheme { + -fx-background-color: white; + -fx-text-fill: black; +} + +.separator-text *.line { + -fx-border-color: white; +} + +.separator-inspect *.line { + -fx-border-color: linear-gradient(transparent 0%, orange 50%, transparent 100%); + -fx-border-width: 1; +} + +.separator-card *.line { + -fx-border-color: linear-gradient(to right, transparent 0%, orangered 50%, transparent 100%); + -fx-border-width: 1; +} + +.separator-history *.line { + -fx-border-color: grey; + -fx-border-width: 1; +} + +.inspectionPanel-VBox { + -fx-background-color: rgba(0, 0, 0, 0.4); + -fx-padding: 5, 5, 5, 5; + -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.3), 10, 0.5, -5.0, 0.0); +} + +.inspection-card .label { + -fx-text-fill: white; +} + +.history-panel .label { + -fx-text-fill: white; } .list-view { -fx-background-insets: 0; -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: transparent; } .list-cell { -fx-label-padding: 0 0 0 0; -fx-graphic-text-gap : 0; - -fx-padding: 0 0 0 0; + -fx-padding: 4; + -fx-background-color: transparent; + -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.3), 10, 0.5, -1.0, 1.0); +} + +.list-cell:filled { + -fx-background-color: rgba(0, 0, 0, 0.5); + -fx-background-insets: 4px ; } -.list-cell:filled:even { - -fx-background-color: #3c3e3f; +.person-list .list-cell:filled { + -fx-background-color: #303030; + -fx-background-radius: 5; + -fx-border-width: 3; + -fx-border-color: #f7bf97; + -fx-border-insets: 4px; } -.list-cell:filled:odd { - -fx-background-color: #515658; +.person-list .list-cell:filled:selected #cardPane { + -fx-border-color: #3e7b91; + -fx-border-width: 0; + -fx-border-radius: 0px; } + .list-cell:filled:selected { - -fx-background-color: #424d5f; + -fx-background-color: rgba(0, 0, 0, 0.8); + -fx-border-radius: 0px; } .list-cell:filled:selected #cardPane { -fx-border-color: #3e7b91; - -fx-border-width: 1; + -fx-border-width: 0; + -fx-border-radius: 0px; } .list-cell .label { -fx-text-fill: white; } -.cell_big_label { - -fx-font-family: "Segoe UI Semibold"; - -fx-font-size: 16px; +.cell_bigger_label { + -fx-font-family: 'MinionPro-Bold'; + -fx-font-size: 21px; -fx-text-fill: #010504; } +.person-VBox .cell_big_label { + -fx-padding: 5 5 5 5; + -fx-font-family: 'MinionPro-Bold'; + -fx-font-size: 16px; + -fx-text-fill: white; +} + .cell_small_label { - -fx-font-family: "Segoe UI"; - -fx-font-size: 13px; + -fx-font-family: "MinionPro-Semibold"; + -fx-font-size: 12px; -fx-text-fill: #010504; } @@ -137,18 +235,18 @@ } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); + -fx-background-color: transparent; + /*-fx-border-color: derive(-fx-bg-color, 10%);*/ -fx-border-top-width: 1px; } .status-bar { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: derive(-fx-bg-color, 10%); } .result-display { -fx-background-color: transparent; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: 'MinionPro-Medium'; -fx-font-size: 13pt; -fx-text-fill: white; } @@ -158,8 +256,8 @@ } .status-bar .label { - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; + -fx-font-family: 'Bender'; + -fx-text-fill: black; -fx-padding: 4px; -fx-pref-height: 30px; } @@ -198,7 +296,7 @@ .menu-bar .label { -fx-font-size: 14pt; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: 'Minion Pro'; -fx-text-fill: white; -fx-opacity: 0.9; } @@ -218,7 +316,7 @@ -fx-border-width: 2; -fx-background-radius: 0; -fx-background-color: #1d1d1d; - -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-family: 'Bender-Bold', Helvetica, Arial, sans-serif; -fx-font-size: 11pt; -fx-text-fill: #d8d8d8; -fx-background-insets: 0 0 0 0, 0, 1, 2; @@ -282,12 +380,28 @@ } .scroll-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: transparent; + -fx-background-radius: 20; + -fx-padding: 1; +} + + +.list-view .corner{ + -fx-background-color:transparent; +} + +.scroll-bar:horizontal { + -fx-pref-height: 13.5; +} + +.scroll-bar:vertical { + -fx-pref-width: 13.5; } .scroll-bar .thumb { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: derive(-fx-shadow-color, 10%); -fx-background-insets: 3; + -fx-background-radius: 20; } .scroll-bar .increment-button, .scroll-bar .decrement-button { @@ -323,7 +437,7 @@ -fx-border-color: #383838 #383838 #ffffff #383838; -fx-border-insets: 0; -fx-border-width: 1; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: Helvetica; -fx-font-size: 13pt; -fx-text-fill: white; } @@ -343,10 +457,10 @@ } #tags .label { - -fx-text-fill: white; - -fx-background-color: #3e7b91; + -fx-text-fill: black; + -fx-background-color: lightcyan; -fx-padding: 1 3 1 3; -fx-border-radius: 2; -fx-background-radius: 2; - -fx-font-size: 11; + -fx-font-size: 8; } diff --git a/src/main/resources/view/InspectionPanel.fxml b/src/main/resources/view/InspectionPanel.fxml new file mode 100644 index 00000000000..db4c1804a3d --- /dev/null +++ b/src/main/resources/view/InspectionPanel.fxml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/LoanHistoryCard.fxml b/src/main/resources/view/LoanHistoryCard.fxml new file mode 100644 index 00000000000..f158b5306d5 --- /dev/null +++ b/src/main/resources/view/LoanHistoryCard.fxml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index a431648f6c0..a0730551dba 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -3,16 +3,13 @@ - - - - + + title="Address App" minWidth="900" minHeight="600" onCloseRequest="#handleExit"> @@ -23,37 +20,27 @@ - - - - - - - - - - + - - - - - - - - - - - - + + - + + + + + + diff --git a/src/main/resources/view/NoteListCard.fxml b/src/main/resources/view/NoteListCard.fxml new file mode 100644 index 00000000000..1c6b0b59bc8 --- /dev/null +++ b/src/main/resources/view/NoteListCard.fxml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/NoteListPanel.fxml b/src/main/resources/view/NoteListPanel.fxml new file mode 100644 index 00000000000..9808f3310ab --- /dev/null +++ b/src/main/resources/view/NoteListPanel.fxml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index 59dd41cec34..a8bd30c832c 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -2,36 +2,37 @@ - - - - - - - - - + + + + + + + + + - + - - - - - - + + + + + + + + + + diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml index 8836d323cc5..02cfc3b7496 100644 --- a/src/main/resources/view/PersonListPanel.fxml +++ b/src/main/resources/view/PersonListPanel.fxml @@ -3,6 +3,41 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml index 58d5ad3dc56..2173e64bf0b 100644 --- a/src/main/resources/view/ResultDisplay.fxml +++ b/src/main/resources/view/ResultDisplay.fxml @@ -5,5 +5,5 @@ -