diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000000..9a34f87a4fc
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,7 @@
+{
+ "java.debug.settings.onBuildFailureProceed": true,
+ "java.configuration.updateBuildConfiguration": "interactive",
+ "githubPullRequests.ignoredPullRequestBranches": [
+ "master"
+ ]
+}
diff --git a/README.md b/README.md
index 13f5c77403f..f9c7b531c2f 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,22 @@
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
+[![CI Status](https://github.com/AY2324S1-CS2103T-W12-4/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2324S1-CS2103T-W12-4/tp/actions)
+
+## What is TimetaBRO?
+
+TimetaBRO is a desktop application targetted at NUS students
+for storing and keeping track of your peers' schedules.
+
+Created by NUS students, for NUS students,
+TimetaBRO aims to help you easily compare others' schedules with your own
+and efficiently find common free times with your friends or project group mates.
+
+With TimetaBRO, forget hopping around screenshots of timetables to compare them,
+or wasting time going back and forth with your friends trying to find a meetup time!
+Instead, store all your friend's timetables on one platform and save yourself from all the hassle.
+Schedule tracking and coordination has never been easier, faster, or more convenient!
+
+## Product Screenshot:
![Ui](docs/images/Ui.png)
-* This is **a sample project for Software Engineering (SE) students**.
- Example usages:
- * as a starting point of a course project (as opposed to writing everything from scratch)
- * as a case study
-* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details.
- * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big.
- * It comes with a **reasonable level of user and developer documentation**.
-* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...).
-* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**.
-* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info.
+* For the detailed documentation of this project, see the **[TimetaBRO Website](https://ay2324s1-cs2103T-w12-4.github.io/tp/)**.
+* This project is based on the AddressBook-Level3 project created by the **[SE-EDU initiative](https://se-education.org)**.
diff --git a/bin/main/images/address_book_32.png b/bin/main/images/address_book_32.png
new file mode 100644
index 00000000000..29810cf1fd9
Binary files /dev/null and b/bin/main/images/address_book_32.png differ
diff --git a/bin/main/images/calendar.png b/bin/main/images/calendar.png
new file mode 100644
index 00000000000..8b2bdf4f1c1
Binary files /dev/null and b/bin/main/images/calendar.png differ
diff --git a/bin/main/images/clock.png b/bin/main/images/clock.png
new file mode 100644
index 00000000000..0807cbf6451
Binary files /dev/null and b/bin/main/images/clock.png differ
diff --git a/bin/main/images/fail.png b/bin/main/images/fail.png
new file mode 100644
index 00000000000..6daf01290dd
Binary files /dev/null and b/bin/main/images/fail.png differ
diff --git a/bin/main/images/help_icon.png b/bin/main/images/help_icon.png
new file mode 100644
index 00000000000..f8e80d6c1c5
Binary files /dev/null and b/bin/main/images/help_icon.png differ
diff --git a/bin/main/images/info_icon.png b/bin/main/images/info_icon.png
new file mode 100644
index 00000000000..f8cef714095
Binary files /dev/null and b/bin/main/images/info_icon.png differ
diff --git a/bin/main/seedu/address/AppParameters.class b/bin/main/seedu/address/AppParameters.class
new file mode 100644
index 00000000000..50f2008a4e3
Binary files /dev/null and b/bin/main/seedu/address/AppParameters.class differ
diff --git a/bin/main/seedu/address/Main.class b/bin/main/seedu/address/Main.class
new file mode 100644
index 00000000000..b96a3f8b880
Binary files /dev/null and b/bin/main/seedu/address/Main.class differ
diff --git a/bin/main/seedu/address/MainApp.class b/bin/main/seedu/address/MainApp.class
new file mode 100644
index 00000000000..d02c4073a02
Binary files /dev/null and b/bin/main/seedu/address/MainApp.class differ
diff --git a/bin/main/seedu/address/commons/core/Config.class b/bin/main/seedu/address/commons/core/Config.class
new file mode 100644
index 00000000000..fa02726ece7
Binary files /dev/null and b/bin/main/seedu/address/commons/core/Config.class differ
diff --git a/bin/main/seedu/address/commons/core/GuiSettings.class b/bin/main/seedu/address/commons/core/GuiSettings.class
new file mode 100644
index 00000000000..64a72810595
Binary files /dev/null and b/bin/main/seedu/address/commons/core/GuiSettings.class differ
diff --git a/bin/main/seedu/address/commons/core/LogsCenter.class b/bin/main/seedu/address/commons/core/LogsCenter.class
new file mode 100644
index 00000000000..a8afef6c261
Binary files /dev/null and b/bin/main/seedu/address/commons/core/LogsCenter.class differ
diff --git a/bin/main/seedu/address/commons/core/Version.class b/bin/main/seedu/address/commons/core/Version.class
new file mode 100644
index 00000000000..46bda2a930f
Binary files /dev/null and b/bin/main/seedu/address/commons/core/Version.class differ
diff --git a/bin/main/seedu/address/commons/core/index/Index.class b/bin/main/seedu/address/commons/core/index/Index.class
new file mode 100644
index 00000000000..1c335d6414d
Binary files /dev/null and b/bin/main/seedu/address/commons/core/index/Index.class differ
diff --git a/bin/main/seedu/address/commons/exceptions/DataLoadingException.class b/bin/main/seedu/address/commons/exceptions/DataLoadingException.class
new file mode 100644
index 00000000000..b94c62ba3e3
Binary files /dev/null and b/bin/main/seedu/address/commons/exceptions/DataLoadingException.class differ
diff --git a/bin/main/seedu/address/commons/exceptions/IllegalValueException.class b/bin/main/seedu/address/commons/exceptions/IllegalValueException.class
new file mode 100644
index 00000000000..af85ad747a5
Binary files /dev/null and b/bin/main/seedu/address/commons/exceptions/IllegalValueException.class differ
diff --git a/bin/main/seedu/address/commons/util/AppUtil.class b/bin/main/seedu/address/commons/util/AppUtil.class
new file mode 100644
index 00000000000..9d58ca88144
Binary files /dev/null and b/bin/main/seedu/address/commons/util/AppUtil.class differ
diff --git a/bin/main/seedu/address/commons/util/CollectionUtil.class b/bin/main/seedu/address/commons/util/CollectionUtil.class
new file mode 100644
index 00000000000..810ea10f8b0
Binary files /dev/null and b/bin/main/seedu/address/commons/util/CollectionUtil.class differ
diff --git a/bin/main/seedu/address/commons/util/ConfigUtil.class b/bin/main/seedu/address/commons/util/ConfigUtil.class
new file mode 100644
index 00000000000..7ff4b0b5f7c
Binary files /dev/null and b/bin/main/seedu/address/commons/util/ConfigUtil.class differ
diff --git a/bin/main/seedu/address/commons/util/FileUtil.class b/bin/main/seedu/address/commons/util/FileUtil.class
new file mode 100644
index 00000000000..bcd9bc038f8
Binary files /dev/null and b/bin/main/seedu/address/commons/util/FileUtil.class differ
diff --git a/bin/main/seedu/address/commons/util/JsonUtil$LevelDeserializer.class b/bin/main/seedu/address/commons/util/JsonUtil$LevelDeserializer.class
new file mode 100644
index 00000000000..7cb75c8e974
Binary files /dev/null and b/bin/main/seedu/address/commons/util/JsonUtil$LevelDeserializer.class differ
diff --git a/bin/main/seedu/address/commons/util/JsonUtil.class b/bin/main/seedu/address/commons/util/JsonUtil.class
new file mode 100644
index 00000000000..c2c06f94ee7
Binary files /dev/null and b/bin/main/seedu/address/commons/util/JsonUtil.class differ
diff --git a/bin/main/seedu/address/commons/util/StringUtil.class b/bin/main/seedu/address/commons/util/StringUtil.class
new file mode 100644
index 00000000000..02d815d7414
Binary files /dev/null and b/bin/main/seedu/address/commons/util/StringUtil.class differ
diff --git a/bin/main/seedu/address/commons/util/ToStringBuilder.class b/bin/main/seedu/address/commons/util/ToStringBuilder.class
new file mode 100644
index 00000000000..6527c9dd69f
Binary files /dev/null and b/bin/main/seedu/address/commons/util/ToStringBuilder.class differ
diff --git a/bin/main/seedu/address/logic/Logic.class b/bin/main/seedu/address/logic/Logic.class
new file mode 100644
index 00000000000..e934c0c51d9
Binary files /dev/null and b/bin/main/seedu/address/logic/Logic.class differ
diff --git a/bin/main/seedu/address/logic/LogicManager.class b/bin/main/seedu/address/logic/LogicManager.class
new file mode 100644
index 00000000000..8b1b7b843e8
Binary files /dev/null and b/bin/main/seedu/address/logic/LogicManager.class differ
diff --git a/bin/main/seedu/address/logic/Messages.class b/bin/main/seedu/address/logic/Messages.class
new file mode 100644
index 00000000000..2ddc6e39f17
Binary files /dev/null and b/bin/main/seedu/address/logic/Messages.class differ
diff --git a/bin/main/seedu/address/logic/commands/AddCommand.class b/bin/main/seedu/address/logic/commands/AddCommand.class
new file mode 100644
index 00000000000..d077015959d
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/AddCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/AddEventCommand.class b/bin/main/seedu/address/logic/commands/AddEventCommand.class
new file mode 100644
index 00000000000..5688915f082
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/AddEventCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/AddScheduleCommand.class b/bin/main/seedu/address/logic/commands/AddScheduleCommand.class
new file mode 100644
index 00000000000..fc43c710e75
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/AddScheduleCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/ClearCommand.class b/bin/main/seedu/address/logic/commands/ClearCommand.class
new file mode 100644
index 00000000000..355245b0537
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/ClearCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/Command.class b/bin/main/seedu/address/logic/commands/Command.class
new file mode 100644
index 00000000000..e35925db868
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/Command.class differ
diff --git a/bin/main/seedu/address/logic/commands/CommandResult.class b/bin/main/seedu/address/logic/commands/CommandResult.class
new file mode 100644
index 00000000000..0f41f0129b4
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/CommandResult.class differ
diff --git a/bin/main/seedu/address/logic/commands/CommonFreetimeCommand.class b/bin/main/seedu/address/logic/commands/CommonFreetimeCommand.class
new file mode 100644
index 00000000000..1400cb346b7
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/CommonFreetimeCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/DeleteCommand.class b/bin/main/seedu/address/logic/commands/DeleteCommand.class
new file mode 100644
index 00000000000..a10fad259c2
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/DeleteCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/ExitCommand.class b/bin/main/seedu/address/logic/commands/ExitCommand.class
new file mode 100644
index 00000000000..89275470fff
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/ExitCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/FindCommand.class b/bin/main/seedu/address/logic/commands/FindCommand.class
new file mode 100644
index 00000000000..0e575ed4f2b
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/FindCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/HelpCommand.class b/bin/main/seedu/address/logic/commands/HelpCommand.class
new file mode 100644
index 00000000000..6aed08ca513
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/HelpCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/ListCommand.class b/bin/main/seedu/address/logic/commands/ListCommand.class
new file mode 100644
index 00000000000..19c761b6d0a
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/ListCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/RemoveEventCommand.class b/bin/main/seedu/address/logic/commands/RemoveEventCommand.class
new file mode 100644
index 00000000000..c59ecf9593f
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/RemoveEventCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/RemoveReminderCommand.class b/bin/main/seedu/address/logic/commands/RemoveReminderCommand.class
new file mode 100644
index 00000000000..2edc272e62b
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/RemoveReminderCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/RemoveScheduleCommand.class b/bin/main/seedu/address/logic/commands/RemoveScheduleCommand.class
new file mode 100644
index 00000000000..fd84d07b0d3
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/RemoveScheduleCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/SetReminderCommand.class b/bin/main/seedu/address/logic/commands/SetReminderCommand.class
new file mode 100644
index 00000000000..231f049d4bf
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/SetReminderCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/edit/EditCommand.class b/bin/main/seedu/address/logic/commands/edit/EditCommand.class
new file mode 100644
index 00000000000..54b4c183290
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/edit/EditCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/edit/EditPersonDescriptor.class b/bin/main/seedu/address/logic/commands/edit/EditPersonDescriptor.class
new file mode 100644
index 00000000000..d9f81e15f59
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/edit/EditPersonDescriptor.class differ
diff --git a/bin/main/seedu/address/logic/commands/edit/EditUserCommand.class b/bin/main/seedu/address/logic/commands/edit/EditUserCommand.class
new file mode 100644
index 00000000000..dfc2e23ba33
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/edit/EditUserCommand.class differ
diff --git a/bin/main/seedu/address/logic/commands/edit/EditUserDescriptor.class b/bin/main/seedu/address/logic/commands/edit/EditUserDescriptor.class
new file mode 100644
index 00000000000..e4a17fd8336
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/edit/EditUserDescriptor.class differ
diff --git a/bin/main/seedu/address/logic/commands/exceptions/CommandException.class b/bin/main/seedu/address/logic/commands/exceptions/CommandException.class
new file mode 100644
index 00000000000..40ff643fea0
Binary files /dev/null and b/bin/main/seedu/address/logic/commands/exceptions/CommandException.class differ
diff --git a/bin/main/seedu/address/logic/parser/AddCommandParser.class b/bin/main/seedu/address/logic/parser/AddCommandParser.class
new file mode 100644
index 00000000000..d2c06157c18
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/AddCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/AddEventCommandParser.class b/bin/main/seedu/address/logic/parser/AddEventCommandParser.class
new file mode 100644
index 00000000000..dbb0978c501
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/AddEventCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/AddScheduleCommandParser.class b/bin/main/seedu/address/logic/parser/AddScheduleCommandParser.class
new file mode 100644
index 00000000000..f7e71029d69
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/AddScheduleCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/AddressBookParser.class b/bin/main/seedu/address/logic/parser/AddressBookParser.class
new file mode 100644
index 00000000000..01abeba9fd6
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/AddressBookParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/ArgumentMultimap.class b/bin/main/seedu/address/logic/parser/ArgumentMultimap.class
new file mode 100644
index 00000000000..767d6d013aa
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/ArgumentMultimap.class differ
diff --git a/bin/main/seedu/address/logic/parser/ArgumentTokenizer$PrefixPosition.class b/bin/main/seedu/address/logic/parser/ArgumentTokenizer$PrefixPosition.class
new file mode 100644
index 00000000000..8762d805bfe
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/ArgumentTokenizer$PrefixPosition.class differ
diff --git a/bin/main/seedu/address/logic/parser/ArgumentTokenizer.class b/bin/main/seedu/address/logic/parser/ArgumentTokenizer.class
new file mode 100644
index 00000000000..7294286c996
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/ArgumentTokenizer.class differ
diff --git a/bin/main/seedu/address/logic/parser/CliSyntax.class b/bin/main/seedu/address/logic/parser/CliSyntax.class
new file mode 100644
index 00000000000..90fcb89b26c
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/CliSyntax.class differ
diff --git a/bin/main/seedu/address/logic/parser/CommonFreetimeCommandParser.class b/bin/main/seedu/address/logic/parser/CommonFreetimeCommandParser.class
new file mode 100644
index 00000000000..01d6fc12ff9
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/CommonFreetimeCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/DeleteCommandParser.class b/bin/main/seedu/address/logic/parser/DeleteCommandParser.class
new file mode 100644
index 00000000000..f895138d75c
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/DeleteCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/EditCommandParser.class b/bin/main/seedu/address/logic/parser/EditCommandParser.class
new file mode 100644
index 00000000000..1ab01150bfd
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/EditCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/EditUserCommandParser.class b/bin/main/seedu/address/logic/parser/EditUserCommandParser.class
new file mode 100644
index 00000000000..672f9ba7eb4
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/EditUserCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/FindCommandParser.class b/bin/main/seedu/address/logic/parser/FindCommandParser.class
new file mode 100644
index 00000000000..16297c9c7ed
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/FindCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/Parser.class b/bin/main/seedu/address/logic/parser/Parser.class
new file mode 100644
index 00000000000..982a9364d07
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/Parser.class differ
diff --git a/bin/main/seedu/address/logic/parser/ParserUtil.class b/bin/main/seedu/address/logic/parser/ParserUtil.class
new file mode 100644
index 00000000000..2fed01728c4
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/ParserUtil.class differ
diff --git a/bin/main/seedu/address/logic/parser/Prefix.class b/bin/main/seedu/address/logic/parser/Prefix.class
new file mode 100644
index 00000000000..6129b2f8ccc
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/Prefix.class differ
diff --git a/bin/main/seedu/address/logic/parser/RemoveEventCommandParser.class b/bin/main/seedu/address/logic/parser/RemoveEventCommandParser.class
new file mode 100644
index 00000000000..2ff2a660107
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/RemoveEventCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/RemoveReminderCommandParser.class b/bin/main/seedu/address/logic/parser/RemoveReminderCommandParser.class
new file mode 100644
index 00000000000..4f47de254ec
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/RemoveReminderCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/RemoveScheduleCommandParser.class b/bin/main/seedu/address/logic/parser/RemoveScheduleCommandParser.class
new file mode 100644
index 00000000000..5442eaa4100
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/RemoveScheduleCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/SetReminderCommandParser.class b/bin/main/seedu/address/logic/parser/SetReminderCommandParser.class
new file mode 100644
index 00000000000..4d8bfd73639
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/SetReminderCommandParser.class differ
diff --git a/bin/main/seedu/address/logic/parser/exceptions/ParseException.class b/bin/main/seedu/address/logic/parser/exceptions/ParseException.class
new file mode 100644
index 00000000000..747cbb8b53d
Binary files /dev/null and b/bin/main/seedu/address/logic/parser/exceptions/ParseException.class differ
diff --git a/bin/main/seedu/address/model/AddressBook.class b/bin/main/seedu/address/model/AddressBook.class
new file mode 100644
index 00000000000..9af1cb5b435
Binary files /dev/null and b/bin/main/seedu/address/model/AddressBook.class differ
diff --git a/bin/main/seedu/address/model/Model.class b/bin/main/seedu/address/model/Model.class
new file mode 100644
index 00000000000..79012f60ac9
Binary files /dev/null and b/bin/main/seedu/address/model/Model.class differ
diff --git a/bin/main/seedu/address/model/ModelManager.class b/bin/main/seedu/address/model/ModelManager.class
new file mode 100644
index 00000000000..a37c03f6e44
Binary files /dev/null and b/bin/main/seedu/address/model/ModelManager.class differ
diff --git a/bin/main/seedu/address/model/ReadOnlyAddressBook.class b/bin/main/seedu/address/model/ReadOnlyAddressBook.class
new file mode 100644
index 00000000000..dd6812d84a6
Binary files /dev/null and b/bin/main/seedu/address/model/ReadOnlyAddressBook.class differ
diff --git a/bin/main/seedu/address/model/person/Address.class b/bin/main/seedu/address/model/person/Address.class
new file mode 100644
index 00000000000..b526a41965d
Binary files /dev/null and b/bin/main/seedu/address/model/person/Address.class differ
diff --git a/bin/main/seedu/address/model/person/Birthday.class b/bin/main/seedu/address/model/person/Birthday.class
new file mode 100644
index 00000000000..4ec5f9965fb
Binary files /dev/null and b/bin/main/seedu/address/model/person/Birthday.class differ
diff --git a/bin/main/seedu/address/model/person/Email.class b/bin/main/seedu/address/model/person/Email.class
new file mode 100644
index 00000000000..209325e4765
Binary files /dev/null and b/bin/main/seedu/address/model/person/Email.class differ
diff --git a/bin/main/seedu/address/model/person/Name.class b/bin/main/seedu/address/model/person/Name.class
new file mode 100644
index 00000000000..f89a47e0c11
Binary files /dev/null and b/bin/main/seedu/address/model/person/Name.class differ
diff --git a/bin/main/seedu/address/model/person/NameContainsKeywordsPredicate.class b/bin/main/seedu/address/model/person/NameContainsKeywordsPredicate.class
new file mode 100644
index 00000000000..f569350cca1
Binary files /dev/null and b/bin/main/seedu/address/model/person/NameContainsKeywordsPredicate.class differ
diff --git a/bin/main/seedu/address/model/person/Person.class b/bin/main/seedu/address/model/person/Person.class
new file mode 100644
index 00000000000..2664d7da685
Binary files /dev/null and b/bin/main/seedu/address/model/person/Person.class differ
diff --git a/bin/main/seedu/address/model/person/Phone.class b/bin/main/seedu/address/model/person/Phone.class
new file mode 100644
index 00000000000..8cfe5871f34
Binary files /dev/null and b/bin/main/seedu/address/model/person/Phone.class differ
diff --git a/bin/main/seedu/address/model/person/UniquePersonList.class b/bin/main/seedu/address/model/person/UniquePersonList.class
new file mode 100644
index 00000000000..1ea1ed14051
Binary files /dev/null and b/bin/main/seedu/address/model/person/UniquePersonList.class differ
diff --git a/bin/main/seedu/address/model/person/exceptions/DuplicatePersonException.class b/bin/main/seedu/address/model/person/exceptions/DuplicatePersonException.class
new file mode 100644
index 00000000000..b93c81ba426
Binary files /dev/null and b/bin/main/seedu/address/model/person/exceptions/DuplicatePersonException.class differ
diff --git a/bin/main/seedu/address/model/person/exceptions/PersonNotFoundException.class b/bin/main/seedu/address/model/person/exceptions/PersonNotFoundException.class
new file mode 100644
index 00000000000..305ea567649
Binary files /dev/null and b/bin/main/seedu/address/model/person/exceptions/PersonNotFoundException.class differ
diff --git a/bin/main/seedu/address/model/person/timetable/Cca.class b/bin/main/seedu/address/model/person/timetable/Cca.class
new file mode 100644
index 00000000000..bb49dbe61d8
Binary files /dev/null and b/bin/main/seedu/address/model/person/timetable/Cca.class differ
diff --git a/bin/main/seedu/address/model/person/timetable/DatedEvent.class b/bin/main/seedu/address/model/person/timetable/DatedEvent.class
new file mode 100644
index 00000000000..65aa87d569e
Binary files /dev/null and b/bin/main/seedu/address/model/person/timetable/DatedEvent.class differ
diff --git a/bin/main/seedu/address/model/person/timetable/FreeTime.class b/bin/main/seedu/address/model/person/timetable/FreeTime.class
new file mode 100644
index 00000000000..e72116dd19a
Binary files /dev/null and b/bin/main/seedu/address/model/person/timetable/FreeTime.class differ
diff --git a/bin/main/seedu/address/model/person/timetable/HalfHourBlocks.class b/bin/main/seedu/address/model/person/timetable/HalfHourBlocks.class
new file mode 100644
index 00000000000..d4b8754ec9f
Binary files /dev/null and b/bin/main/seedu/address/model/person/timetable/HalfHourBlocks.class differ
diff --git a/bin/main/seedu/address/model/person/timetable/Module.class b/bin/main/seedu/address/model/person/timetable/Module.class
new file mode 100644
index 00000000000..b5c680e4659
Binary files /dev/null and b/bin/main/seedu/address/model/person/timetable/Module.class differ
diff --git a/bin/main/seedu/address/model/person/timetable/Schedule.class b/bin/main/seedu/address/model/person/timetable/Schedule.class
new file mode 100644
index 00000000000..6898b1ccad5
Binary files /dev/null and b/bin/main/seedu/address/model/person/timetable/Schedule.class differ
diff --git a/bin/main/seedu/address/model/person/timetable/TimeBlock.class b/bin/main/seedu/address/model/person/timetable/TimeBlock.class
new file mode 100644
index 00000000000..611a8890a3b
Binary files /dev/null and b/bin/main/seedu/address/model/person/timetable/TimeBlock.class differ
diff --git a/bin/main/seedu/address/model/tag/Tag.class b/bin/main/seedu/address/model/tag/Tag.class
new file mode 100644
index 00000000000..c812c5309e3
Binary files /dev/null and b/bin/main/seedu/address/model/tag/Tag.class differ
diff --git a/bin/main/seedu/address/model/user/ReadOnlyUserData.class b/bin/main/seedu/address/model/user/ReadOnlyUserData.class
new file mode 100644
index 00000000000..064d762214d
Binary files /dev/null and b/bin/main/seedu/address/model/user/ReadOnlyUserData.class differ
diff --git a/bin/main/seedu/address/model/user/ReadOnlyUserPrefs.class b/bin/main/seedu/address/model/user/ReadOnlyUserPrefs.class
new file mode 100644
index 00000000000..0853f5de0a2
Binary files /dev/null and b/bin/main/seedu/address/model/user/ReadOnlyUserPrefs.class differ
diff --git a/bin/main/seedu/address/model/user/User.class b/bin/main/seedu/address/model/user/User.class
new file mode 100644
index 00000000000..b5a7b0ba11e
Binary files /dev/null and b/bin/main/seedu/address/model/user/User.class differ
diff --git a/bin/main/seedu/address/model/user/UserData.class b/bin/main/seedu/address/model/user/UserData.class
new file mode 100644
index 00000000000..87ee0eb2349
Binary files /dev/null and b/bin/main/seedu/address/model/user/UserData.class differ
diff --git a/bin/main/seedu/address/model/user/UserPrefs.class b/bin/main/seedu/address/model/user/UserPrefs.class
new file mode 100644
index 00000000000..dbaac65bd30
Binary files /dev/null and b/bin/main/seedu/address/model/user/UserPrefs.class differ
diff --git a/bin/main/seedu/address/model/util/SampleDataUtil.class b/bin/main/seedu/address/model/util/SampleDataUtil.class
new file mode 100644
index 00000000000..7357763f9cf
Binary files /dev/null and b/bin/main/seedu/address/model/util/SampleDataUtil.class differ
diff --git a/bin/main/seedu/address/storage/AddressBookStorage.class b/bin/main/seedu/address/storage/AddressBookStorage.class
new file mode 100644
index 00000000000..30604d3aecd
Binary files /dev/null and b/bin/main/seedu/address/storage/AddressBookStorage.class differ
diff --git a/bin/main/seedu/address/storage/JsonAdaptedPerson.class b/bin/main/seedu/address/storage/JsonAdaptedPerson.class
new file mode 100644
index 00000000000..a3b2ab12883
Binary files /dev/null and b/bin/main/seedu/address/storage/JsonAdaptedPerson.class differ
diff --git a/bin/main/seedu/address/storage/JsonAdaptedTag.class b/bin/main/seedu/address/storage/JsonAdaptedTag.class
new file mode 100644
index 00000000000..c5fb1f3c8cd
Binary files /dev/null and b/bin/main/seedu/address/storage/JsonAdaptedTag.class differ
diff --git a/bin/main/seedu/address/storage/JsonAdaptedUser.class b/bin/main/seedu/address/storage/JsonAdaptedUser.class
new file mode 100644
index 00000000000..9b2bca64ad4
Binary files /dev/null and b/bin/main/seedu/address/storage/JsonAdaptedUser.class differ
diff --git a/bin/main/seedu/address/storage/JsonAddressBookStorage.class b/bin/main/seedu/address/storage/JsonAddressBookStorage.class
new file mode 100644
index 00000000000..4e7a7f906ea
Binary files /dev/null and b/bin/main/seedu/address/storage/JsonAddressBookStorage.class differ
diff --git a/bin/main/seedu/address/storage/JsonSerializableAddressBook.class b/bin/main/seedu/address/storage/JsonSerializableAddressBook.class
new file mode 100644
index 00000000000..75189bea49c
Binary files /dev/null and b/bin/main/seedu/address/storage/JsonSerializableAddressBook.class differ
diff --git a/bin/main/seedu/address/storage/JsonSerializableUserData.class b/bin/main/seedu/address/storage/JsonSerializableUserData.class
new file mode 100644
index 00000000000..040be71b27a
Binary files /dev/null and b/bin/main/seedu/address/storage/JsonSerializableUserData.class differ
diff --git a/bin/main/seedu/address/storage/JsonUserDataStorage.class b/bin/main/seedu/address/storage/JsonUserDataStorage.class
new file mode 100644
index 00000000000..3870bdc7cae
Binary files /dev/null and b/bin/main/seedu/address/storage/JsonUserDataStorage.class differ
diff --git a/bin/main/seedu/address/storage/JsonUserPrefsStorage.class b/bin/main/seedu/address/storage/JsonUserPrefsStorage.class
new file mode 100644
index 00000000000..632901370cb
Binary files /dev/null and b/bin/main/seedu/address/storage/JsonUserPrefsStorage.class differ
diff --git a/bin/main/seedu/address/storage/Storage.class b/bin/main/seedu/address/storage/Storage.class
new file mode 100644
index 00000000000..d7f867f9804
Binary files /dev/null and b/bin/main/seedu/address/storage/Storage.class differ
diff --git a/bin/main/seedu/address/storage/StorageManager.class b/bin/main/seedu/address/storage/StorageManager.class
new file mode 100644
index 00000000000..b0d59c0b549
Binary files /dev/null and b/bin/main/seedu/address/storage/StorageManager.class differ
diff --git a/bin/main/seedu/address/storage/UserDataStorage.class b/bin/main/seedu/address/storage/UserDataStorage.class
new file mode 100644
index 00000000000..4843121eb61
Binary files /dev/null and b/bin/main/seedu/address/storage/UserDataStorage.class differ
diff --git a/bin/main/seedu/address/storage/UserPrefsStorage.class b/bin/main/seedu/address/storage/UserPrefsStorage.class
new file mode 100644
index 00000000000..436d49b7e8a
Binary files /dev/null and b/bin/main/seedu/address/storage/UserPrefsStorage.class differ
diff --git a/bin/main/seedu/address/storage/timetable/JsonAdaptedCca.class b/bin/main/seedu/address/storage/timetable/JsonAdaptedCca.class
new file mode 100644
index 00000000000..bde8a191772
Binary files /dev/null and b/bin/main/seedu/address/storage/timetable/JsonAdaptedCca.class differ
diff --git a/bin/main/seedu/address/storage/timetable/JsonAdaptedDatedEvent.class b/bin/main/seedu/address/storage/timetable/JsonAdaptedDatedEvent.class
new file mode 100644
index 00000000000..0d7d4034406
Binary files /dev/null and b/bin/main/seedu/address/storage/timetable/JsonAdaptedDatedEvent.class differ
diff --git a/bin/main/seedu/address/storage/timetable/JsonAdaptedModule.class b/bin/main/seedu/address/storage/timetable/JsonAdaptedModule.class
new file mode 100644
index 00000000000..63a0a970cce
Binary files /dev/null and b/bin/main/seedu/address/storage/timetable/JsonAdaptedModule.class differ
diff --git a/bin/main/seedu/address/storage/timetable/JsonAdaptedSchedule.class b/bin/main/seedu/address/storage/timetable/JsonAdaptedSchedule.class
new file mode 100644
index 00000000000..ff37b416fc3
Binary files /dev/null and b/bin/main/seedu/address/storage/timetable/JsonAdaptedSchedule.class differ
diff --git a/bin/main/seedu/address/ui/CommandBox$CommandExecutor.class b/bin/main/seedu/address/ui/CommandBox$CommandExecutor.class
new file mode 100644
index 00000000000..efc51a94329
Binary files /dev/null and b/bin/main/seedu/address/ui/CommandBox$CommandExecutor.class differ
diff --git a/bin/main/seedu/address/ui/CommandBox.class b/bin/main/seedu/address/ui/CommandBox.class
new file mode 100644
index 00000000000..c814f7be526
Binary files /dev/null and b/bin/main/seedu/address/ui/CommandBox.class differ
diff --git a/bin/main/seedu/address/ui/HelpWindow.class b/bin/main/seedu/address/ui/HelpWindow.class
new file mode 100644
index 00000000000..f805cf37ee1
Binary files /dev/null and b/bin/main/seedu/address/ui/HelpWindow.class differ
diff --git a/bin/main/seedu/address/ui/ListCellSelectedEvent.class b/bin/main/seedu/address/ui/ListCellSelectedEvent.class
new file mode 100644
index 00000000000..15598a57c9a
Binary files /dev/null and b/bin/main/seedu/address/ui/ListCellSelectedEvent.class differ
diff --git a/bin/main/seedu/address/ui/MainWindow$1.class b/bin/main/seedu/address/ui/MainWindow$1.class
new file mode 100644
index 00000000000..dcc9fdf7dba
Binary files /dev/null and b/bin/main/seedu/address/ui/MainWindow$1.class differ
diff --git a/bin/main/seedu/address/ui/MainWindow.class b/bin/main/seedu/address/ui/MainWindow.class
new file mode 100644
index 00000000000..4ca9cdb76ce
Binary files /dev/null and b/bin/main/seedu/address/ui/MainWindow.class differ
diff --git a/bin/main/seedu/address/ui/PersonCard.class b/bin/main/seedu/address/ui/PersonCard.class
new file mode 100644
index 00000000000..6d1edf2f766
Binary files /dev/null and b/bin/main/seedu/address/ui/PersonCard.class differ
diff --git a/bin/main/seedu/address/ui/PersonListPanel$PersonListViewCell.class b/bin/main/seedu/address/ui/PersonListPanel$PersonListViewCell.class
new file mode 100644
index 00000000000..3d24b04cf56
Binary files /dev/null and b/bin/main/seedu/address/ui/PersonListPanel$PersonListViewCell.class differ
diff --git a/bin/main/seedu/address/ui/PersonListPanel.class b/bin/main/seedu/address/ui/PersonListPanel.class
new file mode 100644
index 00000000000..38ba0191973
Binary files /dev/null and b/bin/main/seedu/address/ui/PersonListPanel.class differ
diff --git a/bin/main/seedu/address/ui/Reminder.class b/bin/main/seedu/address/ui/Reminder.class
new file mode 100644
index 00000000000..dd94a475a7c
Binary files /dev/null and b/bin/main/seedu/address/ui/Reminder.class differ
diff --git a/bin/main/seedu/address/ui/ResultDisplay.class b/bin/main/seedu/address/ui/ResultDisplay.class
new file mode 100644
index 00000000000..a6c347ebb17
Binary files /dev/null and b/bin/main/seedu/address/ui/ResultDisplay.class differ
diff --git a/bin/main/seedu/address/ui/SelectedFriendCard.class b/bin/main/seedu/address/ui/SelectedFriendCard.class
new file mode 100644
index 00000000000..36e57a9c90d
Binary files /dev/null and b/bin/main/seedu/address/ui/SelectedFriendCard.class differ
diff --git a/bin/main/seedu/address/ui/StatusBarFooter.class b/bin/main/seedu/address/ui/StatusBarFooter.class
new file mode 100644
index 00000000000..939a09aa487
Binary files /dev/null and b/bin/main/seedu/address/ui/StatusBarFooter.class differ
diff --git a/bin/main/seedu/address/ui/Ui.class b/bin/main/seedu/address/ui/Ui.class
new file mode 100644
index 00000000000..8778b647196
Binary files /dev/null and b/bin/main/seedu/address/ui/Ui.class differ
diff --git a/bin/main/seedu/address/ui/UiManager.class b/bin/main/seedu/address/ui/UiManager.class
new file mode 100644
index 00000000000..93522e5100c
Binary files /dev/null and b/bin/main/seedu/address/ui/UiManager.class differ
diff --git a/bin/main/seedu/address/ui/UiPart.class b/bin/main/seedu/address/ui/UiPart.class
new file mode 100644
index 00000000000..f93f50d1be8
Binary files /dev/null and b/bin/main/seedu/address/ui/UiPart.class differ
diff --git a/bin/main/seedu/address/ui/UserCard.class b/bin/main/seedu/address/ui/UserCard.class
new file mode 100644
index 00000000000..2c9746ec84b
Binary files /dev/null and b/bin/main/seedu/address/ui/UserCard.class differ
diff --git a/bin/main/view/CommandBox.fxml b/bin/main/view/CommandBox.fxml
new file mode 100644
index 00000000000..124283a392e
--- /dev/null
+++ b/bin/main/view/CommandBox.fxml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/DarkTheme.css b/bin/main/view/DarkTheme.css
new file mode 100644
index 00000000000..3554508dcb0
--- /dev/null
+++ b/bin/main/view/DarkTheme.css
@@ -0,0 +1,443 @@
+.background {
+ -fx-background-color: derive(#1d1d1d, 20%);
+ background-color: #383838; /* Used in the default.html file */
+}
+
+.label {
+ -fx-font-size: 11pt;
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-text-fill: #555555;
+ -fx-opacity: 0.9;
+}
+
+.label-bright {
+ -fx-font-size: 11pt;
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-text-fill: white;
+ -fx-opacity: 1;
+}
+
+.label-header {
+ -fx-font-size: 32pt;
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: white;
+ -fx-opacity: 1;
+}
+
+.text-field {
+ -fx-font-size: 12pt;
+ -fx-font-family: "Segoe UI Semibold";
+}
+
+.tab-pane {
+ -fx-padding: 0 0 0 1;
+}
+
+.tab-pane .tab-header-area {
+ -fx-padding: 0 0 0 0;
+ -fx-min-height: 0;
+ -fx-max-height: 0;
+}
+
+.table-view {
+ -fx-base: #1d1d1d;
+ -fx-control-inner-background: #1d1d1d;
+ -fx-background-color: #1d1d1d;
+ -fx-table-cell-border-color: transparent;
+ -fx-table-header-border-color: transparent;
+ -fx-padding: 5;
+}
+
+.table-view .column-header-background {
+ -fx-background-color: transparent;
+}
+
+.table-view .column-header, .table-view .filler {
+ -fx-size: 35;
+ -fx-border-width: 0 0 1 0;
+ -fx-background-color: transparent;
+ -fx-border-color:
+ transparent
+ transparent
+ derive(-fx-base, 80%)
+ transparent;
+ -fx-border-insets: 0 10 1 0;
+}
+
+.table-view .column-header .label {
+ -fx-font-size: 20pt;
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: white;
+ -fx-alignment: center-left;
+ -fx-opacity: 1;
+}
+
+.table-view:focused .table-row-cell:filled:focused:selected {
+ -fx-background-color: -fx-focus-color;
+}
+
+.split-pane:horizontal .split-pane-divider {
+ -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-border-color: transparent transparent transparent #4d4d4d;
+}
+
+.split-pane {
+ -fx-border-radius: 1;
+ -fx-border-width: 1;
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
+.list-view {
+ -fx-background-insets: 0;
+ -fx-padding: 0;
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
+.list-cell {
+ -fx-label-padding: 0 0 0 0;
+ -fx-graphic-text-gap : 0;
+ -fx-padding: 0 0 0 0;
+}
+
+.list-cell:filled:even {
+ -fx-background-color: #3c3e3f;
+}
+
+.list-cell:filled:odd {
+ -fx-background-color: #515658;
+}
+
+.list-cell:filled:selected {
+ -fx-background-color: #424d5f;
+}
+
+.list-cell:filled:selected #cardPane {
+ -fx-border-color: #3e7b91;
+ -fx-border-width: 1;
+}
+
+.list-cell .label {
+ -fx-text-fill: white;
+}
+
+.cell_big_label {
+ -fx-font-family: "Segoe UI Semibold";
+ -fx-font-size: 16px;
+ -fx-text-fill: #010504;
+}
+
+.cell_small_label {
+ -fx-font-family: "Segoe UI";
+ -fx-font-size: 13px;
+ -fx-text-fill: #010504;
+}
+
+.stack-pane {
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
+.pane-with-border {
+ -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-border-color: derive(#1d1d1d, 10%);
+ -fx-border-top-width: 1px;
+}
+
+.status-bar {
+ -fx-background-color: derive(#1d1d1d, 30%);
+}
+
+.result-display {
+ -fx-background-color: transparent;
+ -fx-font-family: "Segoe UI Light";
+ -fx-font-size: 13pt;
+ -fx-text-fill: white;
+}
+
+.result-display .label {
+ -fx-text-fill: black !important;
+}
+
+.status-bar .label {
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: white;
+ -fx-padding: 4px;
+ -fx-pref-height: 30px;
+}
+
+.status-bar-with-border {
+ -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-border-color: derive(#1d1d1d, 25%);
+ -fx-border-width: 1px;
+}
+
+.status-bar-with-border .label {
+ -fx-text-fill: white;
+}
+
+.grid-pane {
+ -fx-background-color: derive(#1d1d1d, 30%);
+ -fx-border-color: derive(#1d1d1d, 30%);
+ -fx-border-width: 1px;
+}
+
+.grid-pane .stack-pane {
+ -fx-background-color: derive(#1d1d1d, 30%);
+}
+
+.context-menu {
+ -fx-background-color: derive(#1d1d1d, 50%);
+}
+
+.context-menu .label {
+ -fx-text-fill: white;
+}
+
+.menu-bar {
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
+.menu-bar .label {
+ -fx-font-size: 14pt;
+ -fx-font-family: "Segoe UI Light";
+ -fx-text-fill: white;
+ -fx-opacity: 0.9;
+}
+
+.menu .left-container {
+ -fx-background-color: black;
+}
+
+/*
+ * Metro style Push Button
+ * Author: Pedro Duque Vieira
+ * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/
+ */
+.button {
+ -fx-padding: 5 22 5 22;
+ -fx-border-color: #e2e2e2;
+ -fx-border-width: 2;
+ -fx-background-radius: 0;
+ -fx-background-color: #1d1d1d;
+ -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif;
+ -fx-font-size: 11pt;
+ -fx-text-fill: #d8d8d8;
+ -fx-background-insets: 0 0 0 0, 0, 1, 2;
+}
+
+.button:hover {
+ -fx-background-color: #3a3a3a;
+}
+
+.button:pressed, .button:default:hover:pressed {
+ -fx-background-color: white;
+ -fx-text-fill: #1d1d1d;
+}
+
+.button:focused {
+ -fx-border-color: white, white;
+ -fx-border-width: 1, 1;
+ -fx-border-style: solid, segments(1, 1);
+ -fx-border-radius: 0, 0;
+ -fx-border-insets: 1 1 1 1, 0;
+}
+
+.button:disabled, .button:default:disabled {
+ -fx-opacity: 0.4;
+ -fx-background-color: #1d1d1d;
+ -fx-text-fill: white;
+}
+
+.button:default {
+ -fx-background-color: -fx-focus-color;
+ -fx-text-fill: #ffffff;
+}
+
+.button:default:hover {
+ -fx-background-color: derive(-fx-focus-color, 30%);
+}
+
+.dialog-pane {
+ -fx-background-color: #1d1d1d;
+}
+
+.dialog-pane > *.button-bar > *.container {
+ -fx-background-color: #1d1d1d;
+}
+
+.dialog-pane > *.label.content {
+ -fx-font-size: 14px;
+ -fx-font-weight: bold;
+ -fx-text-fill: white;
+}
+
+.dialog-pane:header *.header-panel {
+ -fx-background-color: derive(#1d1d1d, 25%);
+}
+
+.dialog-pane:header *.header-panel *.label {
+ -fx-font-size: 18px;
+ -fx-font-style: italic;
+ -fx-fill: white;
+ -fx-text-fill: white;
+}
+
+.scroll-bar {
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
+.scroll-bar .thumb {
+ -fx-background-color: derive(#1d1d1d, 50%);
+ -fx-background-insets: 3;
+}
+
+.scroll-bar .increment-button, .scroll-bar .decrement-button {
+ -fx-background-color: transparent;
+ -fx-padding: 0 0 0 0;
+}
+
+.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow {
+ -fx-shape: " ";
+}
+
+.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow {
+ -fx-padding: 1 8 1 8;
+}
+
+.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow {
+ -fx-padding: 8 1 8 1;
+}
+
+#cardPane {
+ -fx-background-color: transparent;
+ -fx-border-width: 0;
+}
+
+#commandTypeLabel {
+ -fx-font-size: 11px;
+ -fx-text-fill: #F70D1A;
+}
+
+#commandTextField {
+ -fx-background-color: transparent #383838 transparent #383838;
+ -fx-background-insets: 0;
+ -fx-border-color: #383838 #383838 #ffffff #383838;
+ -fx-border-insets: 0;
+ -fx-border-width: 1;
+ -fx-font-family: "Segoe UI Light";
+ -fx-font-size: 13pt;
+ -fx-text-fill: white;
+}
+
+#filterField, #personListPanel, #personWebpage {
+ -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0);
+}
+
+#resultDisplay .content {
+ -fx-background-color: transparent, #383838, transparent, #383838;
+ -fx-background-radius: 0;
+}
+
+#tags {
+ -fx-hgap: 7;
+ -fx-vgap: 3;
+}
+
+#tags .label {
+ -fx-text-fill: white;
+ -fx-background-color: #3e7b91;
+ -fx-padding: 1 3 1 3;
+ -fx-border-radius: 2;
+ -fx-background-radius: 2;
+ -fx-font-size: 11;
+}
+
+#freeTimes {
+ -fx-hgap: 7;
+ -fx-vgap: 3;
+}
+
+#freeTimes .label {
+ -fx-text-fill: white;
+ -fx-background-color: #6b358c;
+ -fx-padding: 1 3 1 3;
+ -fx-border-radius: 2;
+ -fx-background-radius: 2;
+ -fx-font-size: 11;
+}
+
+#userProfilePlaceholder .label {
+ -fx-text-fill: white;
+}
+
+.ListView {
+ -fx-font-family: "Segoe UI";
+ -fx-font-size: 13px;
+ -fx-text-fill: #010504;
+}
+
+#selectedFriendPlaceholder .label {
+ -fx-text-fill: white;
+}
+
+.display_small_label {
+ -fx-font-family: "Segoe UI";
+ -fx-font-size: 13px;
+ -fx-text-fill: #010504;
+}
+
+.gridPane_display {
+ -fx-grid-lines-visible: true;
+ -fx-stroke: white;
+ -fx-max-width: 100%;
+}
+
+.gridPane_display Line {
+ -fx-stroke: white;
+}
+
+.timetable_heading {
+ -fx-font-size: 14px;
+}
+
+.timetable-timeslot .label {
+ -fx-label-padding: 3 5;
+ -fx-border-radius: 5px;
+ -fx-background-radius: 5px;
+ -fx-font-size: 13px;
+ -fx-text-alignment: center;
+ -fx-overrun: ellipsis;
+}
+
+
+.card-display {
+ -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-border-color: derive(#1d1d1d, 10%);
+ -fx-border-top-width: 1px;
+}
+
+.cca-time-block {
+ -fx-background-color: #9e4f4f;
+}
+
+.module-time-block {
+ -fx-background-color: #3866b0;
+}
+
+.event-time-block {
+ -fx-background-color: #488263;
+}
+
+.background-needed {
+ -fx-background-color: #1d1d1d;
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/Extensions.css b/bin/main/view/Extensions.css
new file mode 100644
index 00000000000..bfe82a85964
--- /dev/null
+++ b/bin/main/view/Extensions.css
@@ -0,0 +1,20 @@
+
+.error {
+ -fx-text-fill: #d06651 !important; /* The error class should always override the default text-fill style */
+}
+
+.list-cell:empty {
+ /* Empty cells will not have alternating colours */
+ -fx-background: #383838;
+}
+
+.tag-selector {
+ -fx-border-width: 1;
+ -fx-border-color: white;
+ -fx-border-radius: 3;
+ -fx-background-radius: 3;
+}
+
+.tooltip-text {
+ -fx-text-fill: white;
+}
diff --git a/bin/main/view/HelpWindow.css b/bin/main/view/HelpWindow.css
new file mode 100644
index 00000000000..a675b4e9e67
--- /dev/null
+++ b/bin/main/view/HelpWindow.css
@@ -0,0 +1,47 @@
+#copyButton, #helpMessage {
+ -fx-text-fill: white;
+}
+
+#copyButton {
+ -fx-background-color: dimgray;
+}
+
+#copyButton:hover {
+ -fx-background-color: gray;
+}
+
+#copyButton:armed {
+ -fx-background-color: darkgray;
+}
+
+#helpMessageContainer {
+ -fx-background-color: derive(#1d1d1d, 20%);
+}
+
+.help_heading {
+ -fx-text-fill: white;
+ -fx-font-size: 25px;
+}
+
+.normal_text {
+ -fx-text-fill: white;
+ -fx-font-size: 15px;
+}
+
+.user_guide_prompt {
+ -fx-background-color: #3d4c57;
+ -fx-border-style: solid;
+ -fx-border-radius: 10px;
+ -fx-border-width: 0px;
+ -fx-background-radius: 10px;
+}
+
+.help_sub_heading {
+ -fx-text-fill: white;
+ -fx-font-size: 18px;
+
+}
+
+.separator {
+ -fx-border-width: 10px;
+}
diff --git a/bin/main/view/HelpWindow.fxml b/bin/main/view/HelpWindow.fxml
new file mode 100644
index 00000000000..dc881e1f89f
--- /dev/null
+++ b/bin/main/view/HelpWindow.fxml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/MainWindow.fxml b/bin/main/view/MainWindow.fxml
new file mode 100644
index 00000000000..9ab68535548
--- /dev/null
+++ b/bin/main/view/MainWindow.fxml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/PersonListCard.fxml b/bin/main/view/PersonListCard.fxml
new file mode 100644
index 00000000000..46bb416562e
--- /dev/null
+++ b/bin/main/view/PersonListCard.fxml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/PersonListPanel.fxml b/bin/main/view/PersonListPanel.fxml
new file mode 100644
index 00000000000..a2e0bee8bae
--- /dev/null
+++ b/bin/main/view/PersonListPanel.fxml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/ResultDisplay.fxml b/bin/main/view/ResultDisplay.fxml
new file mode 100644
index 00000000000..01b691792a9
--- /dev/null
+++ b/bin/main/view/ResultDisplay.fxml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/SelectedFriendCard.fxml b/bin/main/view/SelectedFriendCard.fxml
new file mode 100644
index 00000000000..6f68e045eaa
--- /dev/null
+++ b/bin/main/view/SelectedFriendCard.fxml
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/StatusBarFooter.fxml b/bin/main/view/StatusBarFooter.fxml
new file mode 100644
index 00000000000..7b430f9c6a2
--- /dev/null
+++ b/bin/main/view/StatusBarFooter.fxml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/main/view/UserCard.fxml b/bin/main/view/UserCard.fxml
new file mode 100644
index 00000000000..0226696246d
--- /dev/null
+++ b/bin/main/view/UserCard.fxml
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/test/seedu/address/AppParametersTest$ParametersStub.class b/bin/test/seedu/address/AppParametersTest$ParametersStub.class
new file mode 100644
index 00000000000..caf7e71c6c3
Binary files /dev/null and b/bin/test/seedu/address/AppParametersTest$ParametersStub.class differ
diff --git a/bin/test/seedu/address/AppParametersTest.class b/bin/test/seedu/address/AppParametersTest.class
new file mode 100644
index 00000000000..139da5809d0
Binary files /dev/null and b/bin/test/seedu/address/AppParametersTest.class differ
diff --git a/bin/test/seedu/address/commons/core/ConfigTest.class b/bin/test/seedu/address/commons/core/ConfigTest.class
new file mode 100644
index 00000000000..b88890da4fa
Binary files /dev/null and b/bin/test/seedu/address/commons/core/ConfigTest.class differ
diff --git a/bin/test/seedu/address/commons/core/GuiSettingsTest.class b/bin/test/seedu/address/commons/core/GuiSettingsTest.class
new file mode 100644
index 00000000000..4f05de56f28
Binary files /dev/null and b/bin/test/seedu/address/commons/core/GuiSettingsTest.class differ
diff --git a/bin/test/seedu/address/commons/core/LogsCenterTest.class b/bin/test/seedu/address/commons/core/LogsCenterTest.class
new file mode 100644
index 00000000000..62cfab8ed94
Binary files /dev/null and b/bin/test/seedu/address/commons/core/LogsCenterTest.class differ
diff --git a/bin/test/seedu/address/commons/core/VersionTest.class b/bin/test/seedu/address/commons/core/VersionTest.class
new file mode 100644
index 00000000000..c0b1768f025
Binary files /dev/null and b/bin/test/seedu/address/commons/core/VersionTest.class differ
diff --git a/bin/test/seedu/address/commons/core/index/IndexTest.class b/bin/test/seedu/address/commons/core/index/IndexTest.class
new file mode 100644
index 00000000000..bed3326b43d
Binary files /dev/null and b/bin/test/seedu/address/commons/core/index/IndexTest.class differ
diff --git a/bin/test/seedu/address/commons/util/AppUtilTest.class b/bin/test/seedu/address/commons/util/AppUtilTest.class
new file mode 100644
index 00000000000..4c30db01656
Binary files /dev/null and b/bin/test/seedu/address/commons/util/AppUtilTest.class differ
diff --git a/bin/test/seedu/address/commons/util/CollectionUtilTest.class b/bin/test/seedu/address/commons/util/CollectionUtilTest.class
new file mode 100644
index 00000000000..1ad31c77cf8
Binary files /dev/null and b/bin/test/seedu/address/commons/util/CollectionUtilTest.class differ
diff --git a/bin/test/seedu/address/commons/util/ConfigUtilTest.class b/bin/test/seedu/address/commons/util/ConfigUtilTest.class
new file mode 100644
index 00000000000..dc536153ff2
Binary files /dev/null and b/bin/test/seedu/address/commons/util/ConfigUtilTest.class differ
diff --git a/bin/test/seedu/address/commons/util/FileUtilTest.class b/bin/test/seedu/address/commons/util/FileUtilTest.class
new file mode 100644
index 00000000000..9b907a04983
Binary files /dev/null and b/bin/test/seedu/address/commons/util/FileUtilTest.class differ
diff --git a/bin/test/seedu/address/commons/util/JsonUtilTest.class b/bin/test/seedu/address/commons/util/JsonUtilTest.class
new file mode 100644
index 00000000000..35c622f570b
Binary files /dev/null and b/bin/test/seedu/address/commons/util/JsonUtilTest.class differ
diff --git a/bin/test/seedu/address/commons/util/StringUtilTest.class b/bin/test/seedu/address/commons/util/StringUtilTest.class
new file mode 100644
index 00000000000..e70f07a6a6a
Binary files /dev/null and b/bin/test/seedu/address/commons/util/StringUtilTest.class differ
diff --git a/bin/test/seedu/address/commons/util/ToStringBuilderTest.class b/bin/test/seedu/address/commons/util/ToStringBuilderTest.class
new file mode 100644
index 00000000000..ef90764727e
Binary files /dev/null and b/bin/test/seedu/address/commons/util/ToStringBuilderTest.class differ
diff --git a/bin/test/seedu/address/logic/LogicManagerTest$1.class b/bin/test/seedu/address/logic/LogicManagerTest$1.class
new file mode 100644
index 00000000000..7c209976b97
Binary files /dev/null and b/bin/test/seedu/address/logic/LogicManagerTest$1.class differ
diff --git a/bin/test/seedu/address/logic/LogicManagerTest.class b/bin/test/seedu/address/logic/LogicManagerTest.class
new file mode 100644
index 00000000000..55fd50875f9
Binary files /dev/null and b/bin/test/seedu/address/logic/LogicManagerTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddCommandIntegrationTest.class b/bin/test/seedu/address/logic/commands/AddCommandIntegrationTest.class
new file mode 100644
index 00000000000..0b854245ed1
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddCommandIntegrationTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStub.class b/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStub.class
new file mode 100644
index 00000000000..00ff105e333
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStub.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStubAcceptingPersonAdded.class b/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStubAcceptingPersonAdded.class
new file mode 100644
index 00000000000..2368b2c6cf0
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStubAcceptingPersonAdded.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStubWithPerson.class b/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStubWithPerson.class
new file mode 100644
index 00000000000..058d41f2bae
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddCommandTest$ModelStubWithPerson.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddCommandTest.class b/bin/test/seedu/address/logic/commands/AddCommandTest.class
new file mode 100644
index 00000000000..1118d199212
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddEventCommandTest.class b/bin/test/seedu/address/logic/commands/AddEventCommandTest.class
new file mode 100644
index 00000000000..2fd44979865
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddEventCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/AddScheduleCommandTest.class b/bin/test/seedu/address/logic/commands/AddScheduleCommandTest.class
new file mode 100644
index 00000000000..05fac9aa867
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/AddScheduleCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/ClearCommandTest.class b/bin/test/seedu/address/logic/commands/ClearCommandTest.class
new file mode 100644
index 00000000000..25db9e1a957
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/ClearCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/CommandResultTest.class b/bin/test/seedu/address/logic/commands/CommandResultTest.class
new file mode 100644
index 00000000000..15eec2f9d5a
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/CommandResultTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/CommandTestUtil.class b/bin/test/seedu/address/logic/commands/CommandTestUtil.class
new file mode 100644
index 00000000000..cbc5840d0f8
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/CommandTestUtil.class differ
diff --git a/bin/test/seedu/address/logic/commands/CommonFreetimeCommandTest.class b/bin/test/seedu/address/logic/commands/CommonFreetimeCommandTest.class
new file mode 100644
index 00000000000..3437ff5a215
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/CommonFreetimeCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/DeleteCommandTest.class b/bin/test/seedu/address/logic/commands/DeleteCommandTest.class
new file mode 100644
index 00000000000..cefd3c41111
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/DeleteCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/ExitCommandTest.class b/bin/test/seedu/address/logic/commands/ExitCommandTest.class
new file mode 100644
index 00000000000..30617fd697f
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/ExitCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/FindCommandTest.class b/bin/test/seedu/address/logic/commands/FindCommandTest.class
new file mode 100644
index 00000000000..87a291c39ff
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/FindCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/HelpCommandTest.class b/bin/test/seedu/address/logic/commands/HelpCommandTest.class
new file mode 100644
index 00000000000..2bb50b6ac2f
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/HelpCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/ListCommandTest.class b/bin/test/seedu/address/logic/commands/ListCommandTest.class
new file mode 100644
index 00000000000..a23b6847b0c
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/ListCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/RemoveEventCommandTest.class b/bin/test/seedu/address/logic/commands/RemoveEventCommandTest.class
new file mode 100644
index 00000000000..264fd92d392
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/RemoveEventCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/RemoveReminderCommandTest.class b/bin/test/seedu/address/logic/commands/RemoveReminderCommandTest.class
new file mode 100644
index 00000000000..241f4067e8a
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/RemoveReminderCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/RemoveScheduleCommandTest.class b/bin/test/seedu/address/logic/commands/RemoveScheduleCommandTest.class
new file mode 100644
index 00000000000..f550a84d12d
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/RemoveScheduleCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/SetReminderCommandTest.class b/bin/test/seedu/address/logic/commands/SetReminderCommandTest.class
new file mode 100644
index 00000000000..a70190ebc2c
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/SetReminderCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/edit/EditCommandTest.class b/bin/test/seedu/address/logic/commands/edit/EditCommandTest.class
new file mode 100644
index 00000000000..0045dc073cb
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/edit/EditCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/edit/EditPersonDescriptorTest.class b/bin/test/seedu/address/logic/commands/edit/EditPersonDescriptorTest.class
new file mode 100644
index 00000000000..fe3656a6060
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/edit/EditPersonDescriptorTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/edit/EditUserCommandTest.class b/bin/test/seedu/address/logic/commands/edit/EditUserCommandTest.class
new file mode 100644
index 00000000000..0525b997cd1
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/edit/EditUserCommandTest.class differ
diff --git a/bin/test/seedu/address/logic/commands/edit/EditUserDescriptorTest.class b/bin/test/seedu/address/logic/commands/edit/EditUserDescriptorTest.class
new file mode 100644
index 00000000000..bc60d5f08fd
Binary files /dev/null and b/bin/test/seedu/address/logic/commands/edit/EditUserDescriptorTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/AddCommandParserTest.class b/bin/test/seedu/address/logic/parser/AddCommandParserTest.class
new file mode 100644
index 00000000000..d3474b300c0
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/AddCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/AddEventCommandParserTest.class b/bin/test/seedu/address/logic/parser/AddEventCommandParserTest.class
new file mode 100644
index 00000000000..f7938cd6cea
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/AddEventCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/AddScheduleCommandParserTest.class b/bin/test/seedu/address/logic/parser/AddScheduleCommandParserTest.class
new file mode 100644
index 00000000000..97ceb46f6dc
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/AddScheduleCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/AddressBookParserTest.class b/bin/test/seedu/address/logic/parser/AddressBookParserTest.class
new file mode 100644
index 00000000000..61d66687796
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/AddressBookParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/ArgumentTokenizerTest.class b/bin/test/seedu/address/logic/parser/ArgumentTokenizerTest.class
new file mode 100644
index 00000000000..5db1d6b0119
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/ArgumentTokenizerTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/CommandParserTestUtil.class b/bin/test/seedu/address/logic/parser/CommandParserTestUtil.class
new file mode 100644
index 00000000000..3edb6d785f1
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/CommandParserTestUtil.class differ
diff --git a/bin/test/seedu/address/logic/parser/CommonFreetimeCommandParserTest.class b/bin/test/seedu/address/logic/parser/CommonFreetimeCommandParserTest.class
new file mode 100644
index 00000000000..05409f66efc
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/CommonFreetimeCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/DeleteCommandParserTest.class b/bin/test/seedu/address/logic/parser/DeleteCommandParserTest.class
new file mode 100644
index 00000000000..a232fc07d23
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/DeleteCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/EditCommandParserTest.class b/bin/test/seedu/address/logic/parser/EditCommandParserTest.class
new file mode 100644
index 00000000000..f13a09f049c
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/EditCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/EditUserCommandParserTest.class b/bin/test/seedu/address/logic/parser/EditUserCommandParserTest.class
new file mode 100644
index 00000000000..7a4d7e59011
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/EditUserCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/FindCommandParserTest.class b/bin/test/seedu/address/logic/parser/FindCommandParserTest.class
new file mode 100644
index 00000000000..93df85db04d
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/FindCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/ParserUtilTest.class b/bin/test/seedu/address/logic/parser/ParserUtilTest.class
new file mode 100644
index 00000000000..794e6a56e40
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/ParserUtilTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/RemoveEventCommandParserTest.class b/bin/test/seedu/address/logic/parser/RemoveEventCommandParserTest.class
new file mode 100644
index 00000000000..21c5c9cc7a7
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/RemoveEventCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/RemoveReminderCommandParserTest.class b/bin/test/seedu/address/logic/parser/RemoveReminderCommandParserTest.class
new file mode 100644
index 00000000000..95f367eb244
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/RemoveReminderCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/RemoveScheduleCommandParserTest.class b/bin/test/seedu/address/logic/parser/RemoveScheduleCommandParserTest.class
new file mode 100644
index 00000000000..7942f8100e8
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/RemoveScheduleCommandParserTest.class differ
diff --git a/bin/test/seedu/address/logic/parser/SetReminderCommandParserTest.class b/bin/test/seedu/address/logic/parser/SetReminderCommandParserTest.class
new file mode 100644
index 00000000000..42f93551e38
Binary files /dev/null and b/bin/test/seedu/address/logic/parser/SetReminderCommandParserTest.class differ
diff --git a/bin/test/seedu/address/model/AddressBookTest$AddressBookStub.class b/bin/test/seedu/address/model/AddressBookTest$AddressBookStub.class
new file mode 100644
index 00000000000..7764a8b5dba
Binary files /dev/null and b/bin/test/seedu/address/model/AddressBookTest$AddressBookStub.class differ
diff --git a/bin/test/seedu/address/model/AddressBookTest.class b/bin/test/seedu/address/model/AddressBookTest.class
new file mode 100644
index 00000000000..e5536f1b0b5
Binary files /dev/null and b/bin/test/seedu/address/model/AddressBookTest.class differ
diff --git a/bin/test/seedu/address/model/ModelManagerTest.class b/bin/test/seedu/address/model/ModelManagerTest.class
new file mode 100644
index 00000000000..bf794b2dbf8
Binary files /dev/null and b/bin/test/seedu/address/model/ModelManagerTest.class differ
diff --git a/bin/test/seedu/address/model/person/AddressTest.class b/bin/test/seedu/address/model/person/AddressTest.class
new file mode 100644
index 00000000000..cda7773edc0
Binary files /dev/null and b/bin/test/seedu/address/model/person/AddressTest.class differ
diff --git a/bin/test/seedu/address/model/person/BirthdayTest.class b/bin/test/seedu/address/model/person/BirthdayTest.class
new file mode 100644
index 00000000000..4e32ca74864
Binary files /dev/null and b/bin/test/seedu/address/model/person/BirthdayTest.class differ
diff --git a/bin/test/seedu/address/model/person/EmailTest.class b/bin/test/seedu/address/model/person/EmailTest.class
new file mode 100644
index 00000000000..0ee4720aa9e
Binary files /dev/null and b/bin/test/seedu/address/model/person/EmailTest.class differ
diff --git a/bin/test/seedu/address/model/person/NameContainsKeywordsPredicateTest.class b/bin/test/seedu/address/model/person/NameContainsKeywordsPredicateTest.class
new file mode 100644
index 00000000000..835dfc29aa4
Binary files /dev/null and b/bin/test/seedu/address/model/person/NameContainsKeywordsPredicateTest.class differ
diff --git a/bin/test/seedu/address/model/person/NameTest.class b/bin/test/seedu/address/model/person/NameTest.class
new file mode 100644
index 00000000000..df138ae1453
Binary files /dev/null and b/bin/test/seedu/address/model/person/NameTest.class differ
diff --git a/bin/test/seedu/address/model/person/PersonTest.class b/bin/test/seedu/address/model/person/PersonTest.class
new file mode 100644
index 00000000000..e856452b82b
Binary files /dev/null and b/bin/test/seedu/address/model/person/PersonTest.class differ
diff --git a/bin/test/seedu/address/model/person/PhoneTest.class b/bin/test/seedu/address/model/person/PhoneTest.class
new file mode 100644
index 00000000000..98eaa8da42f
Binary files /dev/null and b/bin/test/seedu/address/model/person/PhoneTest.class differ
diff --git a/bin/test/seedu/address/model/person/UniquePersonListTest.class b/bin/test/seedu/address/model/person/UniquePersonListTest.class
new file mode 100644
index 00000000000..64b7536924e
Binary files /dev/null and b/bin/test/seedu/address/model/person/UniquePersonListTest.class differ
diff --git a/bin/test/seedu/address/model/person/timetable/CcaTest.class b/bin/test/seedu/address/model/person/timetable/CcaTest.class
new file mode 100644
index 00000000000..f9c0f4ea0fe
Binary files /dev/null and b/bin/test/seedu/address/model/person/timetable/CcaTest.class differ
diff --git a/bin/test/seedu/address/model/person/timetable/DatedEventTest.class b/bin/test/seedu/address/model/person/timetable/DatedEventTest.class
new file mode 100644
index 00000000000..474aca6ab36
Binary files /dev/null and b/bin/test/seedu/address/model/person/timetable/DatedEventTest.class differ
diff --git a/bin/test/seedu/address/model/person/timetable/FreeTimeTest.class b/bin/test/seedu/address/model/person/timetable/FreeTimeTest.class
new file mode 100644
index 00000000000..52f263ef9ea
Binary files /dev/null and b/bin/test/seedu/address/model/person/timetable/FreeTimeTest.class differ
diff --git a/bin/test/seedu/address/model/person/timetable/HalfHourBlocksTest.class b/bin/test/seedu/address/model/person/timetable/HalfHourBlocksTest.class
new file mode 100644
index 00000000000..1a20bcf0edf
Binary files /dev/null and b/bin/test/seedu/address/model/person/timetable/HalfHourBlocksTest.class differ
diff --git a/bin/test/seedu/address/model/person/timetable/ModuleTest.class b/bin/test/seedu/address/model/person/timetable/ModuleTest.class
new file mode 100644
index 00000000000..25abb4b912f
Binary files /dev/null and b/bin/test/seedu/address/model/person/timetable/ModuleTest.class differ
diff --git a/bin/test/seedu/address/model/person/timetable/ScheduleTest.class b/bin/test/seedu/address/model/person/timetable/ScheduleTest.class
new file mode 100644
index 00000000000..02c72bb595c
Binary files /dev/null and b/bin/test/seedu/address/model/person/timetable/ScheduleTest.class differ
diff --git a/bin/test/seedu/address/model/person/timetable/TimeBlockTest.class b/bin/test/seedu/address/model/person/timetable/TimeBlockTest.class
new file mode 100644
index 00000000000..0609612dbf9
Binary files /dev/null and b/bin/test/seedu/address/model/person/timetable/TimeBlockTest.class differ
diff --git a/bin/test/seedu/address/model/tag/TagTest.class b/bin/test/seedu/address/model/tag/TagTest.class
new file mode 100644
index 00000000000..a202ea61010
Binary files /dev/null and b/bin/test/seedu/address/model/tag/TagTest.class differ
diff --git a/bin/test/seedu/address/model/user/UserDataTest.class b/bin/test/seedu/address/model/user/UserDataTest.class
new file mode 100644
index 00000000000..f4779219e19
Binary files /dev/null and b/bin/test/seedu/address/model/user/UserDataTest.class differ
diff --git a/bin/test/seedu/address/model/user/UserPrefsTest.class b/bin/test/seedu/address/model/user/UserPrefsTest.class
new file mode 100644
index 00000000000..a64e0653b9c
Binary files /dev/null and b/bin/test/seedu/address/model/user/UserPrefsTest.class differ
diff --git a/bin/test/seedu/address/model/user/UserTest.class b/bin/test/seedu/address/model/user/UserTest.class
new file mode 100644
index 00000000000..9a5428203a5
Binary files /dev/null and b/bin/test/seedu/address/model/user/UserTest.class differ
diff --git a/bin/test/seedu/address/storage/JsonAdaptedPersonTest.class b/bin/test/seedu/address/storage/JsonAdaptedPersonTest.class
new file mode 100644
index 00000000000..e90feaec9d2
Binary files /dev/null and b/bin/test/seedu/address/storage/JsonAdaptedPersonTest.class differ
diff --git a/bin/test/seedu/address/storage/JsonAdaptedUserTest.class b/bin/test/seedu/address/storage/JsonAdaptedUserTest.class
new file mode 100644
index 00000000000..ee14977711c
Binary files /dev/null and b/bin/test/seedu/address/storage/JsonAdaptedUserTest.class differ
diff --git a/bin/test/seedu/address/storage/JsonAddressBookStorageTest.class b/bin/test/seedu/address/storage/JsonAddressBookStorageTest.class
new file mode 100644
index 00000000000..304cb3c87f6
Binary files /dev/null and b/bin/test/seedu/address/storage/JsonAddressBookStorageTest.class differ
diff --git a/bin/test/seedu/address/storage/JsonSerializableAddressBookTest.class b/bin/test/seedu/address/storage/JsonSerializableAddressBookTest.class
new file mode 100644
index 00000000000..5638e39b3c8
Binary files /dev/null and b/bin/test/seedu/address/storage/JsonSerializableAddressBookTest.class differ
diff --git a/bin/test/seedu/address/storage/JsonSerializableUserDataTest.class b/bin/test/seedu/address/storage/JsonSerializableUserDataTest.class
new file mode 100644
index 00000000000..3399b84cabc
Binary files /dev/null and b/bin/test/seedu/address/storage/JsonSerializableUserDataTest.class differ
diff --git a/bin/test/seedu/address/storage/JsonUserDataStorageTest.class b/bin/test/seedu/address/storage/JsonUserDataStorageTest.class
new file mode 100644
index 00000000000..e20cec7666b
Binary files /dev/null and b/bin/test/seedu/address/storage/JsonUserDataStorageTest.class differ
diff --git a/bin/test/seedu/address/storage/JsonUserPrefsStorageTest.class b/bin/test/seedu/address/storage/JsonUserPrefsStorageTest.class
new file mode 100644
index 00000000000..b2daf75ef03
Binary files /dev/null and b/bin/test/seedu/address/storage/JsonUserPrefsStorageTest.class differ
diff --git a/bin/test/seedu/address/storage/StorageManagerTest.class b/bin/test/seedu/address/storage/StorageManagerTest.class
new file mode 100644
index 00000000000..285981bb294
Binary files /dev/null and b/bin/test/seedu/address/storage/StorageManagerTest.class differ
diff --git a/bin/test/seedu/address/storage/timetable/JsonAdaptedCcaTest.class b/bin/test/seedu/address/storage/timetable/JsonAdaptedCcaTest.class
new file mode 100644
index 00000000000..76ad17b46db
Binary files /dev/null and b/bin/test/seedu/address/storage/timetable/JsonAdaptedCcaTest.class differ
diff --git a/bin/test/seedu/address/storage/timetable/JsonAdaptedDatedEventTest.class b/bin/test/seedu/address/storage/timetable/JsonAdaptedDatedEventTest.class
new file mode 100644
index 00000000000..70cf662956c
Binary files /dev/null and b/bin/test/seedu/address/storage/timetable/JsonAdaptedDatedEventTest.class differ
diff --git a/bin/test/seedu/address/storage/timetable/JsonAdaptedModuleTest.class b/bin/test/seedu/address/storage/timetable/JsonAdaptedModuleTest.class
new file mode 100644
index 00000000000..948a55aebea
Binary files /dev/null and b/bin/test/seedu/address/storage/timetable/JsonAdaptedModuleTest.class differ
diff --git a/bin/test/seedu/address/storage/timetable/JsonAdaptedScheduleTest.class b/bin/test/seedu/address/storage/timetable/JsonAdaptedScheduleTest.class
new file mode 100644
index 00000000000..a721e58df7f
Binary files /dev/null and b/bin/test/seedu/address/storage/timetable/JsonAdaptedScheduleTest.class differ
diff --git a/bin/test/seedu/address/testutil/AddressBookBuilder.class b/bin/test/seedu/address/testutil/AddressBookBuilder.class
new file mode 100644
index 00000000000..89a4abc2755
Binary files /dev/null and b/bin/test/seedu/address/testutil/AddressBookBuilder.class differ
diff --git a/bin/test/seedu/address/testutil/Assert.class b/bin/test/seedu/address/testutil/Assert.class
new file mode 100644
index 00000000000..9eba3381576
Binary files /dev/null and b/bin/test/seedu/address/testutil/Assert.class differ
diff --git a/bin/test/seedu/address/testutil/DatedEventsBuilder.class b/bin/test/seedu/address/testutil/DatedEventsBuilder.class
new file mode 100644
index 00000000000..dcb8e486861
Binary files /dev/null and b/bin/test/seedu/address/testutil/DatedEventsBuilder.class differ
diff --git a/bin/test/seedu/address/testutil/EditPersonDescriptorBuilder.class b/bin/test/seedu/address/testutil/EditPersonDescriptorBuilder.class
new file mode 100644
index 00000000000..f8b4d1c17ba
Binary files /dev/null and b/bin/test/seedu/address/testutil/EditPersonDescriptorBuilder.class differ
diff --git a/bin/test/seedu/address/testutil/EditUserDescriptorBuilder.class b/bin/test/seedu/address/testutil/EditUserDescriptorBuilder.class
new file mode 100644
index 00000000000..1b1adc02000
Binary files /dev/null and b/bin/test/seedu/address/testutil/EditUserDescriptorBuilder.class differ
diff --git a/bin/test/seedu/address/testutil/PersonBuilder.class b/bin/test/seedu/address/testutil/PersonBuilder.class
new file mode 100644
index 00000000000..d205b84afe6
Binary files /dev/null and b/bin/test/seedu/address/testutil/PersonBuilder.class differ
diff --git a/bin/test/seedu/address/testutil/PersonUtil.class b/bin/test/seedu/address/testutil/PersonUtil.class
new file mode 100644
index 00000000000..a6aea9b6efd
Binary files /dev/null and b/bin/test/seedu/address/testutil/PersonUtil.class differ
diff --git a/bin/test/seedu/address/testutil/ScheduleBuilder.class b/bin/test/seedu/address/testutil/ScheduleBuilder.class
new file mode 100644
index 00000000000..920e9e6a903
Binary files /dev/null and b/bin/test/seedu/address/testutil/ScheduleBuilder.class differ
diff --git a/bin/test/seedu/address/testutil/SerializableTestClass.class b/bin/test/seedu/address/testutil/SerializableTestClass.class
new file mode 100644
index 00000000000..221e212d5d3
Binary files /dev/null and b/bin/test/seedu/address/testutil/SerializableTestClass.class differ
diff --git a/bin/test/seedu/address/testutil/TestUtil.class b/bin/test/seedu/address/testutil/TestUtil.class
new file mode 100644
index 00000000000..148dc750bdc
Binary files /dev/null and b/bin/test/seedu/address/testutil/TestUtil.class differ
diff --git a/bin/test/seedu/address/testutil/TypicalDatedEvents.class b/bin/test/seedu/address/testutil/TypicalDatedEvents.class
new file mode 100644
index 00000000000..7afabf908a1
Binary files /dev/null and b/bin/test/seedu/address/testutil/TypicalDatedEvents.class differ
diff --git a/bin/test/seedu/address/testutil/TypicalIndexes.class b/bin/test/seedu/address/testutil/TypicalIndexes.class
new file mode 100644
index 00000000000..0fbc16b38a3
Binary files /dev/null and b/bin/test/seedu/address/testutil/TypicalIndexes.class differ
diff --git a/bin/test/seedu/address/testutil/TypicalPersons.class b/bin/test/seedu/address/testutil/TypicalPersons.class
new file mode 100644
index 00000000000..f160d3dcce8
Binary files /dev/null and b/bin/test/seedu/address/testutil/TypicalPersons.class differ
diff --git a/bin/test/seedu/address/testutil/TypicalSchedule.class b/bin/test/seedu/address/testutil/TypicalSchedule.class
new file mode 100644
index 00000000000..e24bb8558eb
Binary files /dev/null and b/bin/test/seedu/address/testutil/TypicalSchedule.class differ
diff --git a/bin/test/seedu/address/testutil/TypicalUsers.class b/bin/test/seedu/address/testutil/TypicalUsers.class
new file mode 100644
index 00000000000..dc0cdb2bd81
Binary files /dev/null and b/bin/test/seedu/address/testutil/TypicalUsers.class differ
diff --git a/bin/test/seedu/address/testutil/UserBuilder.class b/bin/test/seedu/address/testutil/UserBuilder.class
new file mode 100644
index 00000000000..b2e2cae7692
Binary files /dev/null and b/bin/test/seedu/address/testutil/UserBuilder.class differ
diff --git a/bin/test/seedu/address/ui/TestFxmlObject.class b/bin/test/seedu/address/ui/TestFxmlObject.class
new file mode 100644
index 00000000000..0d516b76bbb
Binary files /dev/null and b/bin/test/seedu/address/ui/TestFxmlObject.class differ
diff --git a/bin/test/seedu/address/ui/UiPartTest$TestUiPart.class b/bin/test/seedu/address/ui/UiPartTest$TestUiPart.class
new file mode 100644
index 00000000000..49c70ef126a
Binary files /dev/null and b/bin/test/seedu/address/ui/UiPartTest$TestUiPart.class differ
diff --git a/bin/test/seedu/address/ui/UiPartTest.class b/bin/test/seedu/address/ui/UiPartTest.class
new file mode 100644
index 00000000000..24faea7cb10
Binary files /dev/null and b/bin/test/seedu/address/ui/UiPartTest.class differ
diff --git a/bin/test/view/UiPartTest/invalidFile.fxml b/bin/test/view/UiPartTest/invalidFile.fxml
new file mode 100644
index 00000000000..67680946732
--- /dev/null
+++ b/bin/test/view/UiPartTest/invalidFile.fxml
@@ -0,0 +1 @@
+Not a valid FXML file
diff --git a/bin/test/view/UiPartTest/validFile.fxml b/bin/test/view/UiPartTest/validFile.fxml
new file mode 100644
index 00000000000..bab836af0db
--- /dev/null
+++ b/bin/test/view/UiPartTest/validFile.fxml
@@ -0,0 +1,4 @@
+
+
+
+Hello World!
diff --git a/bin/test/view/UiPartTest/validFileWithFxRoot.fxml b/bin/test/view/UiPartTest/validFileWithFxRoot.fxml
new file mode 100644
index 00000000000..1a8b2c9e4d3
--- /dev/null
+++ b/bin/test/view/UiPartTest/validFileWithFxRoot.fxml
@@ -0,0 +1,6 @@
+
+
+
+ Hello World!
+
diff --git a/build.gradle b/build.gradle
index a2951cc709e..6d6fe461168 100644
--- a/build.gradle
+++ b/build.gradle
@@ -20,6 +20,10 @@ checkstyle {
toolVersion = '10.2'
}
+run {
+ enableAssertions = true
+}
+
test {
useJUnitPlatform()
finalizedBy jacocoTestReport
@@ -66,7 +70,7 @@ dependencies {
}
shadowJar {
- archiveFileName = 'addressbook.jar'
+ archiveFileName = 'timetabro.jar'
}
defaultTasks 'clean', 'test'
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 1c9514e966a..2f8d6493146 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -9,51 +9,52 @@ You can reach us at the email `seer[at]comp.nus.edu.sg`
## Project team
-### John Doe
+### Andre Foo En Jie
-
+
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](https://github.com/andrefoo)]
+[[portfolio](team/andrefoo.md)]
-* Role: Project Advisor
+* Role: Model Developer
+* Secondary Role: UG/DG IC
-### Jane Doe
+### Khiew Yiquan, Edric
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/lululwtv)]
+[[portfolio](team/lululwtv.md)]
-* Role: Team Lead
-* Responsibilities: UI
+* Role: Logic Developer
+* Secondary Role: Milestones Tracker
-### Johnny Doe
+### Krista Yeo Su-Anne
-
+
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](http://github.com/kristayeo)]
+[[portfolio](team/kristayeo.md)]
-* Role: Developer
-* Responsibilities: Data
+* Role: UI IC
+* Secondary Role: UG/DG IC
-### Jean Doe
+### Owen Yeo Le Yang
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/owenyeo)]
+[[portfolio](team/owenyeo.md)]
-* Role: Developer
-* Responsibilities: Dev Ops + Threading
+* Role: Storage Developer
+* Secondary Role: Code Quality IC
-### James Doe
+### Teo Zheng Yang
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/teozhengyang)]
+[[portfolio](team/teozhengyang.md)]
-* Role: Developer
-* Responsibilities: UI
+* Role: Test Engineer
+* Secondary Role: Reminder Feature
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 8a861859bfd..e72f03bca14 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -7,26 +7,26 @@ title: Developer Guide
--------------------------------------------------------------------------------------------------------------------
-## **Acknowledgements**
+## **1. Acknowledgements**
-* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well}
+* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org).
--------------------------------------------------------------------------------------------------------------------
-## **Setting up, getting started**
+## **2. Setting up, getting started**
Refer to the guide [_Setting up and getting started_](SettingUp.md).
--------------------------------------------------------------------------------------------------------------------
-## **Design**
+## **3. Design**
:bulb: **Tip:** The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams.
-### Architecture
+### 3.1 Architecture
@@ -36,15 +36,15 @@ Given below is a quick overview of main components and how they interact with ea
**Main components of the architecture**
-**`Main`** (consisting of classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down.
+**`Main`** (consisting of classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2324S1-CS2103T-W12-4/tp/blob/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down.
* At app launch, it initializes the other components in the correct sequence, and connects them up with each other.
* At shut down, it shuts down the other components and invokes cleanup methods where necessary.
The bulk of the app's work is done by the following four components:
-* [**`UI`**](#ui-component): The UI of the App.
+* [**`UI`**](#ui-component): The UI of TimetaBRO.
* [**`Logic`**](#logic-component): The command executor.
-* [**`Model`**](#model-component): Holds the data of the App in memory.
+* [**`Model`**](#model-component): Holds the data of TimetaBRO in memory.
* [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk.
[**`Commons`**](#common-classes) represents a collection of classes used by multiple other components.
@@ -58,52 +58,71 @@ The *Sequence Diagram* below shows how the components interact with each other f
Each of the four main components (also shown in the diagram above),
* defines its *API* in an `interface` with the same name as the Component.
-* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point.
+* implements its functionality using a concrete `{Component Name}Manager` class
+ (which follows the corresponding API `interface` mentioned in the previous point.)
-For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
+For example, the `Logic` component defines its API in the `Logic.java` interface
+and implements its functionality using the `LogicManager.java` class
+which follows the `Logic` interface.
+Other components interact with a given component through its interface rather than the concrete class
+(Reason: to prevent outside component's being coupled to the implementation of a component),
+as illustrated in the (partial) class diagram below.
The sections below give more details of each component.
-### UI component
+### 3.2 UI component
-The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java)
+The **API** of this component is specified in [`Ui.java`](https://github.com/AY2324S1-CS2103T-W12-4/tp/blob/master/src/main/java/seedu/address/ui/Ui.java)
-![Structure of the UI Component](images/UiClassDiagram.png)
+**Description:**
-The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI.
+The `UI` component manages the user interface of TimetaBRO, so it responds to any command to user inputs or action accordingly.
+It uses the JavaFx UI framework.
+The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml)
-The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml)
+**Functionality:**
The `UI` component,
+* displays a `Reminder` popup window at launch.
* executes user commands using the `Logic` component.
* listens for changes to `Model` data so that the UI can be updated with the modified data.
* keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands.
-* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`.
+* depends on some classes in the `Model` component, as it displays `Person` objects residing in the `Model`.
+
+**Component Structure:**
+
+![Structure of the UI Component](images/UiClassDiagram.png)
+
+The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI.
+
+Depending on the state of the application, certain parts of the UI are shown or hidden in `MainWindow`. e.g. `HelpWindow` and `SelectedFriendCard`.
+
+Upon TimetaBRO being launched, the `Reminder` window will be shown on the bottom right hand corner of the desktop's screen.
-### Logic component
+### 3.3 Logic component
-**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java)
+**API** : [`Logic.java`](https://github.com/AY2324S1-CS2103T-W12-4/tp/blob/master/src/main/java/seedu/address/logic/Logic.java)
Here's a (partial) class diagram of the `Logic` component:
-The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1")` API call as an example.
+The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("addschedule user type/cca en/table tennis h/monday 1400 1600")` API call as an example.
-![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png)
+![Interactions Inside the Logic Component for the `delete 1` Command](images/AddScheduleSequenceDiagram.png)
-
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+
:information_source: **Note:** The lifeline for `AddScheduleCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
How the `Logic` component works:
-1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command.
-1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which is executed by the `LogicManager`.
-1. The command can communicate with the `Model` when it is executed (e.g. to delete a person).
-1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`.
+1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `AddScheduleCommandParser`) and uses it to parse the command.
+2. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddScheduleCommand`) which is executed by the `LogicManager`.
+3. The command can communicate with the `Model` when it is executed (e.g. to delete a person).
+4. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`.
Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command:
@@ -113,186 +132,362 @@ How the parsing works:
* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object.
* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing.
-### Model component
-**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java)
+### 3.4 Model component
+**API** : [`Model.java`](https://github.com/AY2324S1-CS2103T-W12-4/tp/blob/master/src/main/java/seedu/address/model/Model.java)
-
+**Description:**
+The `Model` component stores and manages data. It accomplishes this by creating and maintaining a runtime representation of the data utilising Java's Object Oriented Programming abilites. These objects are abstract representations of their real world counterparts and their relationships with each other, simulating their relationships.
-The `Model` component,
+**Functionality:**
-* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object).
-* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.
-* stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects.
-* does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components)
+* `Model` is not dependent on the other packages.
+* `UI` references the `Model` to retrieve relevant information about the `User` and the friends to be displayed on the `MainWindow`.
+* `Logic` component communicates with the `Model` to make modifications based on the commands inputted.
+* `Storage` component refers to the `Model` to store the data on the computer's local memory.
-
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
+**Component structure:**
-
+
-
+The `Model` can be broken down into its subpackages:
+* `person` subpackage:
+ * Represents a `Person` in TimetaBRO and their attributes that the application manages,
+ namely their `Name`, `Phone` number, `Email`, `Address`, `Birthday` and `Schedule`.
+ * `UniquePersonList` ensures that the list of persons does not contain duplicate phone numbers or emails, and supports basic list operations.\
+
+ * `timetable` subpackage within the `person` subpackage encapsulates a person's schedule that includes a list of module timings (`Module`),
+ co-curricular activities timings (`Cca`), and dated events (`DatedEvent`). The `Schedule` class provides functionality
+ to retrieve the schedule for the current week, for a specific day, and to manage free time within the schedule. It also
+ supports operations to add, edit, and delete various time blocks like modules and CCAs, ensuring that there are no
+ overlapping events.
+* `user` subpackage:
+ * The `User` class extends the `Person` class and includes additional management for dated events specific to the user. It allows for setting and removing reminders for events, retrieving events with reminders for the current day, and managing the user's dated events.
+* `util` subpackage:
+ * The `SampleUtilData` utility class populates the `AddressBook` with sample data, providing first-time users a perspective of the application in use.
-### Storage component
+### 3.5 Storage component
**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java)
The `Storage` component,
-* can save both address book data and user preference data in JSON format, and read them back into corresponding objects.
-* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed).
+* can save address book data, user preference, and user data in JSON format, and read them back into corresponding objects.
+* inherits from `AddressBookStorage`, `UserPrefStorage`, and `UserDataStorage` which means it can be treated as any one (if only the functionality of only one is needed).
* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`)
-### Common classes
+
+
+The `Timetable` Classes
+* Allows users to save their friends' and their own timetables in JSON format, and read them back into corresponding objects
+* Depends on the related files in `Model`.
+
+### 3.6 Common classes
Classes used by multiple components are in the `seedu.addressbook.commons` package.
--------------------------------------------------------------------------------------------------------------------
-## **Implementation**
+## **4. Implementation**
This section describes some noteworthy details on how certain features are implemented.
-### \[Proposed\] Undo/redo feature
+### 4.1 Reminder feature
-#### Proposed Implementation
+#### 4.1.1 Description
-The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations:
+The app will display a popup window with reminders for events with reminder set for them and
+for birthdays on the day itself at launch.
-* `VersionedAddressBook#commit()` — Saves the current address book state in its history.
-* `VersionedAddressBook#undo()` — Restores the previous address book state from its history.
-* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history.
+#### 4.1.2 Implementation
+- At launch, the `showReminder` method in `Reminder` is called.
+- `showReminder` calls `returnRemindedEvent` in `User` object to get a list of events with reminders set for them and
+`getBirthdayList` in `Model` object to get a list of birthdays on the day itself.
+- `returnRemindedEvent` will check for dated events with reminders set for them in the `User` object's schedule
+and return a list of them.
+- `getBirthdayList` will call the `getBirthdayList` method in `AddressBook` object.
+- `getBirthdayList` of the `AddressBook` object will return a list of `Person` objects in the `User` object's contacts
+who has birthdays on the day itself.
+- `showReminder` will then display birthday reminders followed by dated events reminders in the relevant textareas.
-These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
+
-Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
+### 4.2 Add friend's schedule feature
-Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state.
+#### 4.2.1 Description
+User can add a recurring event to their friend's timetable, such as a Module or a CCA, to indicate their friend's weekly schedules. This can be done so using the command word `addschedule`, followed by an INDEX (either "user" or an index number reflecting the index of the friend in the friends list), and the following prefixes:
+- `type/`: Schedule type - Module/CCA
+- `en/`: CCA/Module name
+- `h/` : Day, Start Time and End Time (DDD HHMM HHMM)
-![UndoRedoState0](images/UndoRedoState0.png)
+#### 4.2.2 Implementation
+- The `MainWindow#executeCommand()` calls `LogicManager#execute()` method, which proceeds to call `AddressBookParser#parseCommand()` method, which then calls `AddScheduleCommandParser#parse()`.
+- `AddScheduleCommandParser#parse()` parses the information from all the different prefixes, and returns a new instance of `AddScheduleCommand` with the relevant parsed information
+- The `AddScheduleCommand` is then passed up to the `LogicManager`
+- `LogicManager#execute(AddScheduleCommand)` is called, which then calls either `Schedule#addCca()` or `Schedule#addModule()` on a `Person` object depending on the user's input for the `type/` prefix
+- The `Person` object could either be the user, which is retrieved using `Model#getUser()`, or a friend in the addressbook, which is retrieved using `Model#getFilteredPersonList()#get()`, depending on the user's input for `INDEX`
+- The `Schedule` of the corresponding `Person` is then updated
-Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state.
+
-![UndoRedoState1](images/UndoRedoState1.png)
+### 4.3 Edit user details feature
-Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`.
+#### 4.3.1 Description
+User can edit and add their own details, such as their phone numbers and birthdays using this command, with the command word `user` and the following prefixes:
+- `n/`: Name
+- `p/`: Phone number
+- `e/`: E-mail
+- `t/`: Tags
+- `a/`: Address
+- `b/`: Birthday
-![UndoRedoState2](images/UndoRedoState2.png)
+#### 4.3.2 Implementation
+- The `MainWindow#executeCommand()` calls `LogicManager#execute()` method, which proceeds
+to call `AddressBookParser#parseCommand()` method, which then calls `EditUserCommandParser#parse()`.
+- `EditUserCommandParser#parse()` then creates a `EditUserDescriptor` that stores the incoming data to edit user. It stores it using the `set` methods, with `setName()` shown in the diagram below.
+- `EditUserCommandParser` then returns a `EditUserCommand` object using the `EditUserDescriptor`.
+- The `EditUserCommand` is then passed up to `LogicManager`.
+- `LogicManager#execute(editUserCommand)` is called, which then calls `Model#getUser()`. A new `User` object is created with existing user information and incoming data from the `EditUserDescriptor`.
+ - `Model#setUser(editedUser)` is then called to save the updated user into `Model`.
-
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`.
+
-
+
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state.
+### 4.4 Schedule Model feature
-![UndoRedoState3](images/UndoRedoState3.png)
+#### 4.4.1 Description
-
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather
-than attempting to perform the undo.
+The app contains a timetable that helps NUS students keep track of their schedules and their friends' schedules simultaneously.
+The backend of the application contains a model representation of the schedule.
-
+#### 4.4.2 Implementation
-The following sequence diagram shows how the undo operation works:
+![ProblemDomain](images/TimetableProblemDomain.png)
-![UndoSequenceDiagram](images/UndoSequenceDiagram.png)
+In implementing the timetable model, we decided to use the OO domain model (OODM) to model objects in the problem domain.
+Given this description:\
+Each person has 1 schedule. A person is either the user or the user's friends. The schedule is viewed as a weekly timetable,
+which shows only the events for that are happening in the current week. The schedule consists of time blocks that are either recurring (module time slots or cca time slots)
+or non-recurring (dated events). All time slots are displayed with a name, a start time and an end time.
+The user can toggle reminders for one-off events, and can query when they can meet their friends, when both their schedules are free.
+A free time is also a timeslot in the schedule, but is not displayed hence does not have a name.
-
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+![SolutionDomain](images/TimetableSolutionDomain.png)
-
+The `Logic` and `Ui` component interacts with the `timetable` via these command classes:
+* `AddScheduleCommand`, `RemoveScheduleCommand` - adds/removes a cca or module time slot.
+* `AddEventCommand`, `RemoveEventCommand` - adds/removes a dated event time slot.
+* `SetReminderCommand`, `RemoveReminderCommand` - adds/removes reminders from specified dated event.
+* `CommonFreeTimeCommand` - queries for gaps in user and friend'(s) schedules.
-The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state.
+Hence, `Schedule` is implemented as the facade class for the timetable package. The `Logic` and `Ui` components need to access functionality
+deep inside the `timetable` component, but they should not be exposed to its internal details, such as the `TimeBlock` being the
+superclass of `DatedEvent`. Hence, they update/retrieve information about the `timetable` only through `Schedule`. This reduces coupling in
+the design and increases abstraction.
-
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
+### 4.5 Click to View Friend Timetable Feature
-
+#### 4.5.1 Description
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged.
+Whichever list cell of the friend list is clicked on,
+it becomes selected,
+and is displayed on the bottom half of the right hand side of the app.
-![UndoRedoState4](images/UndoRedoState4.png)
+#### 4.5.2 Implementation
-Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow.
+
+
+* The user clicks on the cell within the `ListView` of the friend list.
+* The `onMouseClicked` event is triggered upon the user's click.
+* `PersonListPanel.PersonListViewCell#updateItem()` handles this `MouseEvent` object:
+ * It checks if the event is a single click.
+ * If so, it notes the Person object in the selected list cell and fires a new event `ListCellSelectedEvent` with the selected person.
+* The `ListCellSelectedEvent` extends `Event` saves the selected person object.
+* The event filter in `MainWindow#fillInnerParts()` handles the `ListCellSelectedEvent`
+and retrieves the selected person from it using `ListCellSelectedEvent#getSelectedPerson()`.
+* The selected person is used to create a new `SelectedFriendCard`, which is stored under `friendProfile`.
+* The contents of the `SelectedFriendPlaceHolder` is replaced with the `friendProfile`.
+* The position of the selected friend in the friend list is saved in `selectedFriendPos` for refreshing the display with any changes.
-![UndoRedoState5](images/UndoRedoState5.png)
+--------------------------------------------------------------------------------------------------------------------
-The following activity diagram summarizes what happens when a user executes a new command:
+## **5. Documentation, logging, testing, configuration, dev-ops**
-
+* [Documentation guide](Documentation.md)
+* [Testing guide](Testing.md)
+* [Logging guide](Logging.md)
+* [Configuration guide](Configuration.md)
+* [DevOps guide](DevOps.md)
-#### Design considerations:
+--------------------------------------------------------------------------------------------------------------------
-**Aspect: How undo & redo executes:**
+## **6. Appendix: Requirements**
-* **Alternative 1 (current choice):** Saves the entire address book.
- * Pros: Easy to implement.
- * Cons: May have performance issues in terms of memory usage.
+### 6.1 Product scope
-* **Alternative 2:** Individual command knows how to undo/redo by
- itself.
- * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted).
- * Cons: We must ensure that the implementation of each individual command are correct.
+**Target user profile**:
-_{more aspects and alternatives to be added}_
+* NUS student
+* Has friends whose schedules they need to keep up with
+* Is a part of group projects
+* Has many commitments and is busy
+* Values efficiency and convenience
+
+**Value proposition**:
+
+#### 6.1.1 Problem
+The flexibility of university life grants the ability for students to personalise their schedules,
+but this also means that everyone's timetables are different,
+making it difficult to keep track of your friends and peers activity or availability.
+This can increase difficulty in schedule coordination and arranging meetups.
+Coupled with the many commitments and fast-paced curriculum, this makes it harder than ever to maintain friendships.
+
+In addition, trying to plan meetups or comparing timetables with peers is often time-consuming and troublesome,
+having to go back and forth with friends before a consensus can be reached,
+and hopping around the media of your chats to view the timetables.
+
+#### 6.1.2 How TimetaBRO solves the problem and makes users' lives easier
+
+TimetaBRO allows users to store friend profiles, consisting of their details and schedule, in a friend list.
+It facilitates easy visual comparison between the user's timetable and any selected friend in the list,
+and can search for common free times between the user and either all friends, or a specified friend.
+This effectively eliminates the need to hop between timetables
+and having to waste time conversing with peers to find an ideal meetup time.
+The convenient storing of all the schedules on TimetaBRO,
+as well as reminders on the birthdays of the people in the friend list,
+helps users efficiently manage and keep up with their friendships.
+
+### 6.2 User Stories
+
+**Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
+
+| Priority | As a …​ | I want to …​ | So that I can…​ |
+|-----------|------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|
+| `* * *` | user | add contacts to my list of friends using information like name and contact details | identify them more easily |
+| `* * *` | user | view my list of friends | see all my friends in a glance |
+| `* * *` | user | edit details of my friends | keep their information up to date or change any wrongly filled information |
+| `* * *` | user | search for a specific friend | find their information more easily |
+| `* * *` | user | delete friends from my list of friends | remove people who are no longer my friends and not needed in the list |
+| `* * *` | student | add my timetable to the app | easily access and keep track of my timetable |
+| `* * *` | student | add my friend's timetable to the app | keep track of my friends' schedules |
+| `* * *` | student | identify common free time slots with friends | organize meals or other social activities with them |
+| `* * *` | student | set reminders about events | be well-prepared and organized for all my commitments |
+| `* * *` | student | create events | keep track of important commitments and activities |
+| `* * *` | busy student | receive reminders about events | remember any upcoming events |
+| `* * *` | busy student | receive reminders about my friends' birthdays | remember to wish them |
+| `* * *` | student | edit my timetable | update changes in my timetable |
+| `* * *` | student | view my own timetable | plan my day and easily view my commitments |
+| `* * *` | student | add a class to my timetable | update my timetable as I take on more classes |
+| `* * *` | user | add non-recurring events to my timetable, such as meetups with friends | keep track of all the events happening in my life, not just my classes |
+| `* * *` | student | remove classes that I am no longer taking from my timetable | make sure my timetable is accurate for weeks like Week 13, where some modules no longer have classes |
+| `* * *` | user | remove non-recurring events from my timetable | change my timetable in the event there are changes to my plans |
+| `* *` | student | view my friends' timetables | know more about their day |
+| `* *` | student | visually compare my timetable with that of my friends | quickly identify overlaps or free times |
+| `* *` | student | identify common modules with my friends | attend classes with them |
+
+### 6.3 Use cases
+
+(For all use cases below, the **System** is the `TimetaBRO` and the **Actor** is the `user`, unless specified otherwise)
+
+**Use case: UC01 - Delete a friend**
-### \[Proposed\] Data archiving
+**MSS**
-_{Explain here how the data archiving feature will be implemented}_
+1. User requests to list friends
+2. TimetaBRO shows a list of friends
+3. User requests to delete a specific friend in the list
+4. TimetaBRO deletes the friend
+ Use case ends.
---------------------------------------------------------------------------------------------------------------------
+**Extensions**
-## **Documentation, logging, testing, configuration, dev-ops**
+* 2a. The list is empty.
-* [Documentation guide](Documentation.md)
-* [Testing guide](Testing.md)
-* [Logging guide](Logging.md)
-* [Configuration guide](Configuration.md)
-* [DevOps guide](DevOps.md)
+ Use case ends.
---------------------------------------------------------------------------------------------------------------------
+* 3a. The given index is invalid.
-## **Appendix: Requirements**
+ * 3a1. TimetaBRO shows an invalid index error message.
-### Product scope
+ Use case resumes at step 2.
-**Target user profile**:
-* has a need to manage a significant number of contacts
-* prefer desktop apps over other types
-* can type fast
-* prefers typing to mouse interactions
-* is reasonably comfortable using CLI apps
+**Use case: UC02 - Edit a person's details**
+
+**MSS**
+
+1. User requests to list persons
+2. TimetaBRO shows a list of persons
+3. User requests to edit details of a specific friend in the list
+4. TimetaBRO edits the friend's information accordingly
+
+ Use case ends.
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+**Extensions**
+* 2a. The list is empty.
-### User stories
+ Use case ends.
-Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
+* 3a. User requests to edit user details
-| Priority | As a …​ | I want to …​ | So that I can…​ |
-| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- |
-| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App |
-| `* * *` | user | add a new person | |
-| `* * *` | user | delete a person | remove entries that I no longer need |
-| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list |
-| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident |
-| `*` | user with many persons in the address book | sort persons by name | locate a person easily |
+ Use case resumes at step 4.
-*{More to be added}*
+* 3b. The given index is invalid.
-### Use cases
+ * 3b1. TimetaBRO shows an invalid index error message.
-(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+ Use case resumes at step 2.
+
+* 3c. New details provided is identical to the existing details
+
+ * 3c1. TimetaBRO shows an error message stating that there is no change.
+
+ Use case resumes at step 2.
-**Use case: Delete a person**
+* 3d. New details provided do not adhere to their respective requirements
+
+ * 3d1. TimetaBRO shows an error message with command instructions.
+
+ Use case resumes at step 2.
+
+**Use case: UC03 - Add a friend**
**MSS**
-1. User requests to list persons
-2. AddressBook shows a list of persons
-3. User requests to delete a specific person in the list
-4. AddressBook deletes the person
+1. User requests to list friends
+2. TimetaBRO shows a list of friends
+3. User requests to add a new friend to the list
+4. TimetaBRO adds the new friend
+
+ Use case ends.
+
+**Extensions**
+
+* 3a. Not all the required fields of the friend are provided.
+
+ * 3a1. TimetaBRO shows an error message with command instructions.
+
+ Use case resumes at step 2.
+
+* 3b. Details provided do not adhere to their respective field requirements
+
+ * 3b1. TimetaBRO shows an error message with command instructions.
+
+ Use case resumes at step 2.
+
+**Use case: UC04 - Find a friend**
+
+**MSS**
+
+1. User requests to list friends
+2. TimetaBRO shows a list of friends
+3. User requests to find names containing an inputted keyword
+4. TimetaBRO shows a list of friends whose names contain the keyword
Use case ends.
@@ -302,30 +497,271 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli
Use case ends.
-* 3a. The given index is invalid.
- * 3a1. AddressBook shows an error message.
+**Use case: UC05 - Check for common free times with all friends**
- Use case resumes at step 2.
+**MSS**
+
+1. User requests to list friends
+2. TimetaBRO shows a list of friends
+3. User requests for common free times with entire address book
+4. TimetaBRO shows list of friends with common free times, and their associated common free times
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. User has no free time.
+
+ * 3a1. TimetaBRO indicates to the user that they have no free time.
+
+ Use case ends.
+
+* 3b. User has no common free time with all friends.
+ * 3b1. TimetaBRO indicates to the user that they have no common free time with all their added friends.
+
+ Use case ends.
+
+**Use case: UC06 - Check for common free times with a specific friend**
+
+**MSS**
+
+1. User requests to list friends
+2. TimetaBRO shows a list of friends
+3. User requests for common free times with a specific friend
+4. TimetaBRO lists the common free times the user has with the specific friend
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The list is empty.
+
+ Use case ends.
+
+* 3a. Friend has no common free time with User.
+
+ * 3a1. TimetaBRO indicates to the user that they have no common free time.
+
+ Use case ends.
+
+* 3b. Given index is invalid.
+
+ * 3b1. TimetaBRO shows an invalid index error message.
+
+ Use case resumes at step 2.
+
+* 3c. User has no free time.
+
+ * 3c1. TimetaBRO indicates to the user that they have no free time.
+
+ Use case ends.
+
+**Use case: UC07 - List friends**
+
+**MSS**
+1. User requests to list friends
+2. TimetaBRO shows a list of all friends
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. User has no added friends.
+
+ TimetaBRO shows an empty list. Use case ends.
+
+**Use case: UC08 - Add event to schedule**
+
+**MSS**
+1. User requests to add a new event to indicated person's schedule
+2. TimetaBRO adds the event to the indicated person's schedule
+
+Use case ends.
+
+**Extensions**
+* 1a. User gives an invalid index.
+
+ * TimetaBRO shows an invalid index error message.
+
+ Use case continues from step 1.
+
+* 1b. Not all the required fields of the event are provided.
+
+ * 1b1. TimetaBRO shows an error message with command instructions.
+
+ Use case continues from step 1.
+
+* 1c. Event details inputted do not follow the fields constraints.
+
+ * 1c1. TimetaBRO shows an error message with command instructions.
+
+ Use case continues from step 1.
+
+* 1d. Event overlaps with an existing event.
+
+ * 1d1. TimetaBRO shows an error message stating which event it overlaps with.
+
+ Use case continues from step 1.
+
+* 1e. Index is not provided.
+ * 1e1. TimetaBRO shows an error message.
-*{More to be added}*
+ Use case continues from step 1.
-### Non-Functional Requirements
+**Use case: UC09 - Remove an event from schedule**
+
+**MSS**
+1. User requests to remove an event from a specified person's schedule
+2. TimetaBRO removes the event from the schedule
+
+ Use case ends.
+
+**Extensions**
+* 1a. Given index to specify the person is invalid.
+
+ * 1a1. TimetaBRO shows an invalid index error message.
+
+ Use case continues from step 1.
+
+* 1b. Event details inputted are not in the specified person's schedule.
+
+ * 1b1. TimetaBRO shows an error message.
+
+ Use case continues from step 1.
+
+* 1c. Not all the required fields are provided.
+
+ * 1c1. TimetaBRO shows an error message with command instructions.
+
+ Use case continues from step 1.
+
+* 1d. Index is not provided.
+ * 1d1. TimetaBRO shows an error message.
+
+ Use case continues from step 1.
+
+**Use case: UC10 - Remove reminder from non-recurring event**
+
+**MSS**
+1. User requests to remove a reminder from an event from a specified person's schedule
+2. TimetaBRO removes the reminder from the event
+
+ Use case ends.
+
+**Extensions**
+* 1a. Given index to specify the person is invalid.
+
+ * 1a1. TimetaBRO shows an invalid index error message.
+
+ Use case continues from step 1.
+
+* 1b. Event name inputted are not in the specified person's schedule.
+
+ * 1b1. TimetaBRO shows an error message.
+
+ Use case continues from step 1.
+
+* 1c. Event name not provided.
+
+ * 1c1. TimetaBRO shows an error message with command instructions.
+
+ Use case continues from step 1.
+
+* 1d. Index is not provided.
+ * 1d1. TimetaBRO shows an error message.
+
+ Use case continues from step 1.
+
+**Use case: UC11 - Set reminder for non-recurring event**
+
+**MSS**
+1. User requests to set a reminder for an event in a specified person's schedule
+2. TimetaBRO turns on the reminder for the event
+
+ Use case ends.
+
+**Extensions**
+* 1a. Given index to specify the person is invalid.
+
+ * 1a1. TimetaBRO shows an error message.
+
+ Use case continues from step 1.
+
+* 1b. Event name inputted are not in the specified person's schedule.
+
+ * 1b1. TimetaBRO shows an error message.
+
+ Use case continues from step 1.
+
+* 1c. Event name not provided.
+
+ * 1c1. TimetaBRO shows an error message.
+
+ Use case continues from step 1.
+
+* 1d. Index is not provided.
+ * 1d1. TimetaBRO shows an error message.
+
+ Use case continues from step 1.
+
+**Use case: UC12 - Clear list of friends**
+
+**MSS**
+1. User requests to clear all friends from the list
+2. TimetaBRO clears the entire list of friends
+
+ Use case ends.
+
+**Use case: UC13 - Find help**
+
+**MSS**
+1. User requests for help
+2. TimetaBRO opens a help window with a link to the [TimetaBRO User Guide](UserGuide.md)
+
+ Use case ends.
+
+**Use case: UC14 - Exit the application**
+
+**MSS**
+1. User requests to exit the application.
+2. TimetaBRO saves all data and closes the application.
+
+ Use case ends.
+
+### 6.4 Non-Functional Requirements
1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed.
2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
+4. Should not have a latency of more than 2 seconds to ensure optimal user experience
+5. Should be able to hold up to 10 modules per person without noticeable detriments to the performance of the app
+6. Should ensure the integrity of user data, preventing any data corruption or loss during normal usage.
+7. Should implement appropriate security measures to protect user data from unauthorized access or tampering.
+8. Should be designed with accessibility in mind, ensuring that it is usable by individuals with disabilities, including those who rely on screen readers or keyboard navigation.
+9. Should be able to handle a growing number of contacts without a significant decrease in performance.
-*{More to be added}*
-
-### Glossary
+### 6.5 Glossary
* **Mainstream OS**: Windows, Linux, Unix, OS-X
* **Private contact detail**: A contact detail that is not meant to be shared with others
+* **Performance**: Speed at which the app completes queries.
+* **Tampering**: Modifying data without permission from the owner of said data.
+* **Normal usage**: Day-to-day usage of the app without any errors occurring.
+* **Optimal user experience**: User can utilise all functionality without bugs and lag.
+* **Event**: Time block that can be added to a Person's schedule that is in increments of 30 minutes.
+* **Non-recurring event**: Dated time block that only appears on the specified date.
+* **Recurring event**: Time block that repeats each week on the same day and time.
+* **Timetable**: Grid that is shown in the display profiles that showcases sorted time blocks.
+* **Reminder**: Scheduled notification about an event or birthday on the day itself
--------------------------------------------------------------------------------------------------------------------
-## **Appendix: Instructions for manual testing**
+## **7. Appendix: Instructions for manual testing**
Given below are instructions to test the app manually.
@@ -334,44 +770,227 @@ testers are expected to do more *exploratory* testing.
-### Launch and shutdown
+### 7.1 Add friend
-1. Initial launch
+* Test case: `add n/Amy Bee p/85355255 e/amy@gmail.com a/123, Jurong West Ave 6, #08-111 b/2020-12-01`
+ Expected: A person with the given details is added to the list. List is updated with new person
- 1. Download the jar file and copy into an empty folder
+* Test case: `add Alice Pauline`
+ Expected: Error message is shown. Person is not added to the list. List remains unchanged.
+
+* Other incorrect add commands to try: `add n/Alice Pauline 11111111`, `add n/Alice Pauline 2000-01-01` , `...`
+ Expected: Similar to previous.
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+### 7.2 Add schedule
-1. Saving window preferences
+* Add cca/module to the user's schedule
- 1. Resize the window to an optimum size. Move the window to a different location. Close the window.
+ 1. Test case: `addschedule user type/cca en/table tennis h/monday 1400 1600`
+ Expected: A cca event with the given details is added to the user's schedule. Schedule is updated with new event.
- 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained.
+ 2. Test case: `addschedule user type/module en/cs2103t h/monday 1400 1600`
+ Expected: A module event with the given details is added to the user's schedule. Schedule is updated with new event.
-1. _{ more test cases …​ }_
+ 3. Test case: `addschedule user type/event en/lecture h/monday 1400 1600`
+ Expected: Error message is shown. Event is not added to the schedule. Schedule remains unchanged.
-### Deleting a person
+ 4. Other incorrect add commands to try: `addschedule user type/cca table tennis h/monday 1400 1600` and `addschedule user type/module en/cs2103t h/monday 1400 1650`,`...`
+ Expected: Similar to previous.
-1. Deleting a person while all persons are being shown
+* Add cca/module to a friend's schedule
- 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+ 1. Test case: `addschedule 1 type/cca en/table tennis h/monday 1400 1600`
+ Expected: A cca event with the given details is added to the first friend's schedule. Schedule is updated with new event.
- 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
+ 2. Test case: `addschedule 1 type/module en/cs2103t h/monday 1400 1600`
+ Expected: A module event with the given details is added to the first friend's schedule. Schedule is updated with new event.
- 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
+ 3. Test case: `addschedule 1 type/event en/lecture h/monday 1400 1600`
+ Expected: Error message is shown. Event is not added to the schedule. Schedule remains unchanged.
- 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ 4. Other incorrect add commands to try: `addschedule 1 type/cca table tennis h/monday 1400 1600` and `addschedule 1 type/module en/cs2103t h/monday 1400 1650`, `...`
+ Expected: Similar to previous.
+
+### 7.3 Delete schedule
+
+* Delete cca/module from the user's schedule
+
+ 1. Prerequisites: A cca/module has been added to the user's schedule
+ e.g. `addschedule user type/cca en/table tennis h/monday 1400 1600` and `addschedule user type/module en/cs2103t h/monday 1400 1600`
+
+ 2. Test case: `rmschedule user type/cca en/table tennis`
+ Expected: The cca is deleted from the user's schedule. Schedule is updated with the deletion.
+
+ 3. Test case: `rmschedule user type/module en/cs2103t`
+ Expected: The module is deleted from the user's schedule. Schedule is updated with the deletion.
+
+ 4. Test case: `rmschedule user type/event en/lecture`
+ Expected: Error message is shown. Event is not deleted from the schedule. Schedule remains unchanged.
+
+ 5. Other incorrect delete commands to try: `rmschedule user type/cca table tennis` and `rmschedule user type/cca en/cs2103t`, `...`
+ Expected: Similar to previous.
+
+* Delete cca/module from a friend's schedule
+
+ 1. Prerequisites: A cca/module has been added to the first friend's schedule
+ e.g. `addschedule 1 type/cca en/table tennis h/monday 1400 1600` and `addschedule 1 type/module en/cs2103t h/monday 1400 1600`
+
+ 2. Test case: `rmschedule 1 type/cca en/table tennis`
+ Expected: The cca is deleted from the first friend's schedule. Schedule is updated with the deletion.
+
+ 3. Test case: `rmschedule 1 type/module en/cs2103t`
+ Expected: The module is deleted from the first friend's schedule. Schedule is updated with the deletion.
+
+ 4. Test case: `rmschedule 1 type/event en/lecture`
+ Expected: Error message is shown. Event is not deleted from the schedule. Schedule remains unchanged.
+
+ 5. Other incorrect delete commands to try: `rmschedule 1 type/cca table tennis` and `rmschedule 1 type/cca en/cs2103t`, `...`
+ Expected: Similar to previous.
+
+### 7.4 Add event
+
+* Add non-recurring event to the user's schedule
+
+ 1. Test case: `addevent user en/meeting h/2023-11-15 1400 1600 r/y`
+ Expected: A event with the given details is added to the user's schedule. Schedule is updated with new event.
+
+ 2. Test case: `addevent user meeting h/2023-11-15 1400 1600 r/y`
+ Expected: Error message is shown. Event is not added to the schedule. Schedule remains unchanged.
+
+ 3. Other incorrect add commands to try: `addevent user en/meeting h/monday 1400 1650 r/y` and `addevent en/meeting h/monday 1400 1600 r/y`,`...`
+ Expected: Similar to previous.
+
+* Add non-recurring event to a friend's schedule
+
+ 1. Test case: `addevent 1 en/meeting h/2023-11-15 1400 1600 r/n`
+ Expected: A event with the given details is added to the first friend's schedule. Schedule is updated with new event.
+
+ 2. Test case: `addevent 1 meeting h/2023-11-15 1400 1600 r/n`
+ Expected: Error message is shown. Event is not added to the schedule. Schedule remains unchanged.
+
+ 3. Other incorrect add commands to try: `addevent 1 en/meeting h/monday 1400 1650 r/n` and `addevent en/meeting h/monday 1400 1600 r/n`,`...`
+ Expected: Similar to previous.
+
+### 7.5 Toggle reminder for events
+
+* Set reminder for an event in the user's schedule
+
+ 1. Prerequisites: A non-recurring event has been added to the user's schedule
+ e.g. `addevent user en/meeting h/2023-11-15 1400 1600 r/n`
+
+ 2. Test case: `setReminder meeting`
+ Expected: A reminder is set for the event.
+
+ 3. Test case: `setReminder lecture`
+ Expected: Error message is shown. Reminder is not set for the event.
+
+ 4. Other incorrect reminder commands to try: `setReminder user meeting` and `setReminder en/meeting`, `...`
+ Expected: Similar to previous.
+
+* Remove reminder for an event in th user's schedule
+
+ 1. Prerequisites: A non-recurring event has been added to the user's schedule eg `addevent user en/meeting h/2023-11-15 1400 1600 r/y`
+
+ 2. Test case: `rmReminder meeting`
+ Expected: The reminder is removed for the event.
+
+ 3. Test case: `rmReminder lecture`
+ Expected: Error message is shown. Reminder is not removed for the event.
+
+ 4. Other incorrect reminder commands to try: `rmReminder user meeting` and `rmReminder en/meeting`, `...`
Expected: Similar to previous.
-1. _{ more test cases …​ }_
+### 7.6 Delete event
+
+* Delete non-recurring event from the user's schedule
+
+ 1. Prerequisites: A non-recurring event has been added to the user's schedule
+ e.g. `addevent user en/meeting h/2023-11-15 1400 1600 r/y`
-### Saving data
+ 2. Test case: `rmevent user en/meeting`
+ Expected: The event is deleted from the user's schedule. Schedule is updated with the deletion.
+
+ 3. Test case: `rmevent user en/lecture`
+ Expected: Error message is shown. Event is not deleted from the schedule. Schedule remains unchanged.
+
+ 4. Other incorrect delete commands to try: `rmevent user meeting` and `rmevent en/meeting`, `...`
+ Expected: Similar to previous.
+
+* Delete cca/module from a friend's schedule
+
+ 1. Prerequisites: A non-recurring event has been added to the first friend's schedule
+ e.g. `addevent 1 en/meeting h/2023-11-15 1400 1600 r/y`
+
+ 2. Test case: `rmevent 1 en/meeting`
+ Expected: The event is deleted from the first friend's schedule. Schedule is updated with the deletion.
+
+ 3. Test case: `rmevent 1 en/lecture`
+ Expected: Error message is shown. Event is not deleted from the schedule. Schedule remains unchanged.
+
+ 4. Other incorrect delete commands to try: `rmevent 1 meeting` and `rmevent en/meeting`, `...`
+ Expected: Similar to previous.
+
+### 7.7 Edit user details
+
+* Test case: `user n/xyz`
+ Expected: User's name is updated to xyz provided user's name is not xyz.
+
+* Test case: `user n/xyz` followed by `user n/xyz`
+ Expected: Error message is shown. User's name is not updated to xyz. User's name remains unchanged.
+
+* Other incorrect edit commands to try: `user p/11111111` followed by `user p/11111111`, `user`, `...`
+ Expected: Similar to previous.
+
+### 7.8 Edit a friend's details
+
+* Test case: `edit 1 n/xyz`
+ Expected: First friend's name is updated to xyz provided first friend's name is not xyz.
+
+* Test case: `edit 1 n/xyz` followed by `edit 1 n/xyz`
+ Expected: Error message is shown. First friend's name is not updated to xyz. First friend's name remains unchanged.
+
+* Other incorrect edit commands to try: `edit 1 p/11111111` followed by `edit 1 p/11111111`, `edit 1`, `...`
+ Expected: Similar to previous.
+
+### 7.9 Deleting a person
+
+* Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+
+* Test case: `delete 1`
+ Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
+
+* Test case: `delete 0`
+ Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
+
+* Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous.
+
+### 7.10 Help command
+
+* Test case: `help`
+ Expected: Help menu popup is shown.
+
+### 7.11 Clear command
+
+* Test case: `clear`
+ Expected: All contacts in the list are deleted. List is updated with the deletion and will be empty
+
+### 7.12 Launch and shutdown
+
+* Initial launch
+
+ 1. Download the jar file and copy into an empty folder
+
+ 2. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+
+* Saving window preferences
+
+ 1. Resize the window to an optimum size. Move the window to a different location. Close the window.
+
+ 2. Re-launch the app by double-clicking the jar file.
+ Expected: The most recent window size and location is retained.
-1. Dealing with missing/corrupted data files
+* Shutdown:
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
+ 1. Run the `exit` command. The application should exit and shut down.
-1. _{ more test cases …​ }_
diff --git a/docs/SettingUp.md b/docs/SettingUp.md
index 275445bd551..7928165e3e8 100644
--- a/docs/SettingUp.md
+++ b/docs/SettingUp.md
@@ -45,7 +45,7 @@ If you plan to use Intellij IDEA (highly recommended):
1. **Learn the design**
- When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [AddressBook’s architecture](DeveloperGuide.md#architecture).
+ When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [TimetaBRO’s architecture](DeveloperGuide.md#architecture).
1. **Do the tutorials**
These tutorials will help you get acquainted with the codebase.
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index 57437026c7b..a2e210db85a 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -3,195 +3,1128 @@ layout: page
title: User Guide
---
-AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps.
+## **Introduction - What is TimetaBRO?**
+Welcome to TimetaBRO, your ultimate companion for managing your university life, social interactions, and schedules. It is dedicated to NUS students who have many things on their plate and have trouble arranging meetings with friends and teammates.
+
+In the past, NUS students had to tediously save their friend’s timetables, and compare them to their own. Most of the time, students do not even have the time to compare timetables and have to go through the hassle of coordination through messaging apps.
+
+However, with TimetaBRO, you can now save your friends’ timetables, and ask it when your friends are free! Make scheduling meetups and meetings slick and easy, and while you’re busy scheduling the best dates, you can also save important details about your friends! No more forgetting birthdays or favourite foods, be the best friend you can be!
+
+## **About the user guide**
+This comprehensive user guide will walk you through all the exciting features TimetaBRO has to offer. New to TimetaBRO? Fret not! This guide will walk you through a quickstart to start using TimetaBRO.
+
+This user guide will also provide information about its amazing functionalities in the features section, optimising your use of TimetaBRO even further. There is also a command summary for your convenience!
+
+Additionally, we included FAQs in case you have any additional questions after reading this user guide. There are also hyperlinks around and the table of contents below to guide you to appropriate websites and sections of this user guide. Have fun!
+
+--------------------------------------------------------------------------------------------------------------------
* Table of Contents
{:toc}
--------------------------------------------------------------------------------------------------------------------
-## Quick start
+## **1. Quickstart**
+Before we jump into it, let's make sure that your TimetaBRO is working properly!
+1. Ensure you have Java 11 installed on your computer. This is important!
+ * To check if Java 11 is currently installed, you may follow this [short guide](https://www.baeldung.com/java-check-is-installed).
+ * If Java 11 is not installed, you may follow the installation instructions over [here](https://docs.oracle.com/en/java/javase/11/install/overview-jdk-installation.html#GUID-8677A77F-231A-40F7-98B9-1FD0B48C346A).
+2. Next, download the latest 'timetabro.jar' from [here](https://github.com/AY2324S1-CS2103T-W12-4/tp).
+3. Copy the file to the folder you want to use as a home folder for TimetaBRO.
+4. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. You should see a pop-up. That is your reminder for the events and birthdays for the day!
-1. Ensure you have Java `11` or above installed in your Computer.
+![Ui-labelled](images/ui-startup.png)
+
Image: User interface layout of TimetaBRO upon startup with sample data
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
+Here are some commands you can try:
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+ * `add n/Owen p/91792309 b/ 2001-12-26`:
+ adds a friend named Owen, with phone number 91792309 and birthday on 26 December 2001.
+ Say hello to your new friend!
-1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png)
+ * `addschedule 1 type/module en/CS2101 h/Monday 1200 1400`:
+ adds a module CS2101 into the schedule of the first person in your friends list that occurs on Monday 12pm to 2pm. If you did not change anything else, index 1 should refer to Alex Yeoh. He now has CS2101 in his timetable!
-1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try:
+ * `cft Friday 1200 1400`:
+ Filters your friend list to people who are free on these timings. Alex and Owen should both be free!
- * `list` : Lists all contacts.
+ * `delete 1`:
+ Deletes a person off your list based on their index. It's time to say goodbye to Alex.
- * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book.
+Nice! Now you know the basic commands and have launched TimetaBRO, lets get into the finer details.
- * `delete 3` : Deletes the 3rd contact shown in the current list.
+## **2. TimetaBRO User Interface**
- * `clear` : Deletes all contacts.
+![Ui-labelled](images/ui-breakdown.png)
+
Image: User interface layout breakdown of TimetaBRO
- * `exit` : Exits the app.
+### 2.1. Toolbar
-1. Refer to the [Features](#features) below for details of each command.
+Provides functionality related to accessing the help pop-up or exiting TimetaBRO. Currently, we only have two buttons for you! They are:
+* `File`: Clicking on it will open a dropdown menu, where you can click on `Exit` to exit the application!
+* `Help`: Clicking on it will open another dropdown menu, where you can click on `Help F1` to get a help pop-up! See 2.5 for more info about it.
---------------------------------------------------------------------------------------------------------------------
+### 2.2. Command input box
+
+This is where you can input our cool commands to perform actions on TimetaBRO.
+
+### 2.3. Command feedback box
+
+This displays information related to your entered input in the command input box.
+Errors and success messages all will be here, and they will guide you to use TimetaBRO more effectively, so keep an eye out for them over here!
+
+### 2.4. Friends list
+
+This section displays all your friends in your list by default, and may be filtered display certain friends based on the executed command.
+
+### 2.5 Help pop-up
+
+![Ui-labelled](images/HelpPopUp.png)
+
Image: Help pop-up
+
+This pop-up provides a link to this user guide should you lose the link! There is also a handy `Copy URL` button for you to copy the URL onto your clipboard!
+
+### 2.6. Daily reminder pop-up
+
+![Ui-labelled](images/ui-reminder-popup.png)
+
Image: User interface layout breakdown of TimetaBRO
+
+This reminder pop-up appears when you open the app.
+
+#### 2.6.1. Birthday reminders
-## Features
+This section displays the names of friends whose birthday falls on the date of the day you open TimetaBRO. Remember to wish them happy birthday!
+
+#### 2.6.2. Event reminders
+
+This section displays the non-recurring events you have for the day. They can be hangouts with friends, meetings, exams, and other important events you need reminders for!
+
+### 2.7 Profile display
+
+![Ui-labelled](images/ui-profile-display.png)
+
Image: TimetaBRO profile display breakdown
+
+#### 2.7.1. User display
+
+At all times, this section displays the user profile. This consists of all the user information and the user schedule. Now, you can see your schedule at a glance!
+
+#### 2.7.2. Friend Display
+
+This section is blank by default.
+It displays the profile of whichever friend is selected from the list by clicking on it.
+Upon selection, the list cell of that friend will turn blue, and you can see your friend's details and schedule for quick comparisons.
+
+#### 2.7.3. Display features
+
+##### 2.7.3.1 Timetable layout
+
+The timetable will be displayed under all the profile information, and each profile section is scrollable.
+
+The days of the week are arranged from Monday to Sunday, and the timeslots are arranged from earliest to latest. Hope you like how it looks!
+
+##### 2.7.3.2 Color coding
+
+The event timeslots are color coded according to their type and vibe.
+
+Blue for module (recurring), red for cca (recurring), and green for dated (non-recurring) events. Let TimetaBRO brighten your day up!
+
+##### 2.7.3.3 Event block formation
+
+Each event block consists of the event name on the first line, and the start and end times on the second line. Simple yet elegant!
+
+## **3. Features**
-**:information_source: Notes about the command format:**
+**Notes about the command format:**
-* Words in `UPPER_CASE` are the parameters to be supplied by the user.
+* Words in `UPPER_CASE` are the parameters to be written by you!
e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`.
+* Commands are all case-sensitive! Please follow the format **exactly** as shown in this User Guide!
+
* Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`.
+ e.g. `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`.
* Items with `…`​ after them can be used multiple times including zero times.
e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc.
-* Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
+* Parameters can be in any order you like!
+ e.g. if the command specifies `n/NAME p/PHONE`, the order `p/PHONE n/NAME` is also acceptable.
-* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
+* Extra parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
e.g. if the command specifies `help 123`, it will be interpreted as `help`.
-* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application.
+* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines! Space characters surrounding line-breaks may disappear when copied over to the application.
+
+* Clicking on your friend's name will display their timetable on the bottom half of the right hand side of TimetaBRO.
+
+* Only times in 30-minute intervals are allowed.
+ e.g. `1430` and `1500` will be excepted, `1445` and `2359` will not be valid! If your timings do not fit nicely into 30-minute chunks, try your best to round up/down, and use tags to remind yourself that it's actually at another time. E.g. t/[insert Event Name] 1445
+
-### Viewing help : `help`
+### 3.1 User Commands
-Shows a message explaning how to access the help page.
+#### 3.1.1 Editing User Information: `user`
-![help message](images/helpMessage.png)
+Personalise your profile information, such as your birthday and tags!
-Format: `help`
+Format: `user n/NAME p/PHONE e/EMAIL a/ADDRESS b/BIRTHDAY [t/TAG]...​`
+
+* Click [here](#4-parameters) to find out more about the parameter constraints.
+
+
Tip:
+You can have any number of tags (including 0). Go crazy!
+
+**Successful Command:**
+```
+Edited Your Details: NAME, Phone: PHONE, Email: EMAIL; Address: ADDRESS; Birthday:
+BIRTHDAY; Tags: [TAG]...​
+```
+
+**Unsuccessful Command:**\
+No changes:
+````
+No changes to user.
+````
+
+#### 3.1.2 Adding recurring event to user: `addschedule user`
+
+This command adds a recurring event to your schedule, such as a class or CCA. You might find this command useful if you want to keep track of your weekly commitments!
+
+Format: `addschedule user type/EVENT_TYPE en/EVENT_NAME h/DAY_TIME`
+
+- Adds an event titled `EVENT_NAME`
+- `EVENT_TYPE` is a prefix that can either be `module` or `CCA`.
+- Event date and time will be equal to `DAY_TIME`
+where `DAY_TIME` must be entered in the format `Day HHMM HHMM`.
+`Day` is any day of the week fully spelt out and is case-insensitive, `HHMM` is a 24H time format to indicate the start time and end time!
+- Event names will be changed to all upper case regardless of whether it was keyed it in lower case or upper case.
+- Click [here](#4-parameters) to find out more about the parameter constraints.
+
+
+**Successful Command:**\
+Input:
+```
+addschedule user type/CCA en/Basketball h/Tuesday 1500 1600
+```
+Output:
+```
+New event added:
+CCA:
+BASKETBALL Tuesday 1500 1600 to [Your Name]
+```
+
+**Unsuccessful Command:**\
+If you use the wrong format (i.e. missing prefix, wrong event type),
+this error message will be shown:
+```
+[error message]
+Message Usage:
+addschedule: Adds a schedule to the specified contact.
+Parameters: INDEX type/EVENT_TYPE en/EVENT_NAME h/[DAY_OF_WEEK START_TIME [HHMM]
+END_TIME [HHMM]]
+Example: addschedule 1 type/cca en/Basketball h/Monday 1400 1600
+NOTE: If you want to add a cca/module to yourself, use addschedule user
+Example: addschedule user type/cca en/Basketball h/Monday 1400 1600
+```
+
+The error message at the top of the command feedback above will specify what needs to be changed!
+
+Input:
+```
+addschedule user
+```
+Output:
+```
+Missing prefix(es) for en/ type/ h/ !
+```
-### Adding a person: `add`
+
-Adds a person to the address book.
+:information_source: Notes about the `addschedule` command:
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​`
+* Timeslots added of type `module` will be colored blue, while those of type `CCA` will be colored red.
+* To add an event that lasts until the end of the day (midnight), set the end timing as `2400`. We know it isn't ideal, but bear with us!
+* You will not be allowed to add an event that clashes with any current or future events. We are making sure you don't double book!
-
:bulb: **Tip:**
-A person can have any number of tags (including 0)
-Examples:
-* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01`
-* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal`
+#### 3.1.3 Adding non-recurring event to user: `addevent user`
+
+This command adds a non-recurring event to your schedule. Use it to keep track of meetings, hangouts, and exams!
+
+Format: `addevent user en/EVENT NAME h/DAY_TIME r/REMINDER`
+
+- Adds an event titled `EVENT_NAME`
+- Event date and time will be equal to `DATE_TIME`
+where `DATE_TIME` must be entered in the format `DATE [YYYY-MM-DD] START_TIME [HHMM] END_TIME [HHMM]`. Date must be a valid date!
+- You can set whether you want to enable reminders for this event by inputting `y/n` under `[REMINDER]` so you don't forget them!
+- Event names will be changed to all upper case regardless of whether it was keyed it in lower case or upper case. TimetaBRO is hyping you up!
+- Click [here](#4-parameters) to find out more about the parameter constraints.
+
+**Successful Command:**\
+Input:
+```
+addevent user en/Final Submission h/2023-10-17 1500 1600 r/y
+```
+
+Output:
+```
+New event added:
+Dated Event:
+FINAL SUBMISSION 2023-10-17 1500 1600 to [Your Name]
+```
+
+**Unsuccessful Command:**\
+If you use the wrong format (i.e. missing prefix, wrong event type),
+this error message will be shown:
+```
+[error message]
+Message Usage:
+addevent: Adds a non-recurring event to the calendar.
+Parameters: INDEX en/EVENT_NAME h/[Date [YYYY-MM-DD] StartTime (HHMM) EndTime (HHMM)]
+r/[REMINDER: y/n]
+Example: addevent 1 en/CS2103T Lecture h/2020-03-02 1400 1600 r/y
+Note: Index should be the index of the friend you are adding the dated event to or
+'user' if you would like to add the event to yourself
+```
+
+The error message at the top of the command feedback above will specify what needs to be rectified.\
+e.g.\
+Input:
+```
+addevent user
+```
+
+Output:
+```
+Missing prefix(es) for en/ h/ r/ !
+```
+
+
+
Caution:
+Events added outside the current week are not visible! The application is streamlined for you to see what's ahead in the week, and not anything more.
+
+
+
-### Listing all persons : `list`
+:information_source: Notes about the `addevent` command:
-Shows a list of all persons in the address book.
+* Timeslots added with this command will be green.
+* To add an event that lasts until the end of the day (midnight), set the end timing as `2400`.
+* You will not be allowed to add an event that clashes with any current or future events. Keep those clashes in mind!
-Format: `list`
+
-### Editing a person : `edit`
+#### 3.1.4 Deleting recurring event from user: `rmschedule user`
+
+Removes the specified recurring item from your schedule. Useful for schedule changes!
+ If there are multiple recurring events with the same names, this command will the instance of the event that was added the earliest!
+
+Format: `rmschedule user type/EVENT_TYPE en/EVENT_NAME`
+
+- Click [here](#4-parameters) to find out more about the parameter constraints.
+
+**Successful commands:**\
+Input:
+```
+rmschedule user type/CCA en/Basketball
+```
+
+Output:
+```
+BASKETBALL has been removed from [Your Name]!
+```
+
+**Unsuccessful commands:**\
+If you use the wrong format (i.e. missing prefix),
+this error message will be shown:
+```
+[error message]
+Message Usage:
+rmschedule: Removes an event from the specified contact's calendar.
+Parameters: INDEX type/EVENT_TYPE en/EVENT_NAME
+Example: rmschedule 1 type/cca en/Basketball
+NOTE: If you want to remove an event from yourself, use index user
+Example: rmschedule user type/cca en/Basketball
+```
+
+The error message at the top of the command feedback above will specify what needs to change!\
+e.g.\
+Input:
+```
+rmschedule user
+```
+
+Output:
+```
+Missing prefix(es) for en/ type/ !
+```
+
+If you input an invalid event type, the following error message will be shown:
+```
+Invalid event type!
+Event type must either be 'cca' or 'module'!
+```
-Edits an existing person in the address book.
+
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`
+:information_source: Notes about the `rmschedule` and `rmevent` command:
-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​
-* At least one of the optional fields must be provided.
-* Existing values will be updated to the input values.
-* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative.
-* You can remove all the person’s tags by typing `t/` without
- specifying any tags after it.
+If there are multiple time slots with the same names, the command will remove time slots in the chronological order they were added.
+
+
+
+#### 3.1.5 Deleting non-recurring event from user: `rmevent user`
+
+Removes the specified event from your schedule. You can use it when your plans changes!
+ If there are multiple non-recurring events with the same names, this command will the instance of the event that was added the earliest!
+
+Format: `rmevent user en/EVENT_NAME`
+
+- Click [here](#4-parameters) to find out more about the parameter constraints.
+
+**Successful commands:**\
+Input:
+```
+rmevent user en/CS2103T exam
+```
+
+Output:
+```
+Dated event 'CS2103T EXAM' deleted from your calendar!
+```
+
+**Unsuccessful commands:**\
+If you use the wrong format (i.e. missing prefix),
+this error message will be shown:
+```
+Missing prefix(es) for en/ !
+Message Usage:
+rmevent: Removes an event from the specified contact's calendar.
+Parameters: INDEX en/EVENT_NAME
+Example: rmevent 1 en/CS2103T Final Exam
+NOTE: If you want to remove an event from your calendar, use rmevent user.
+Example: rmevent user en/CS2103T Final Exam
+```
+
+### 3.2 Friend Commands
+
+#### 3.2.1 Adding a friend: `add`
+
+Adds a person to your TimetaBRO friend list. Use it to keep track of the schedules and details of your closest friends or even new acquaintances. Watch as your list grows!
+
+Format: `add n/NAME p/PHONE e/EMAIL a/ADDRESS b/BIRTHDAY [t/TAG]...​`
+
+* Adds a person to your friend list to the last index!
+* You can't add someone with the same birthday and same name, because it's likely that they are the same person! (We know it's not necessarily true, but it's the best way we can do it without collecting your NRIC...)
+* You can't have duplicate phone numbers or emails.
+* Names can be alphanumeric. For Elon Musk's child's sake!
+* Birthdays must be a valid date. We do check the calendars!
+* Click [here](#4-parameters) to find out more about the parameter constraints.
+
+
Tip:
+Your friend can have any number of tags (including 0). Go crazy!
+
+
+**Successful Command:**\
+Output:
+```
+New Person Added: {NAME}, Phone: {PHONE}, Email: {EMAIL}, Address: {ADDRESS},
+Birthday: {BIRTHDAY}, tags: [{TAG}]
+```
+
+**Unsuccessful Command:**\
+Output:
+```
+Invalid command format!
+add: Adds a person to the address book. Parameters: n/NAME p/PHONE e/EMAIL a/ADDRESS
+b/BIRTHDAY [t/TAG]...
+Example: add n/John Doe p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25
+b/2000-01-01 t/friends t/owesMoney
+```
Examples:
-* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively.
-* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags.
+* `add n/John Doe p/98765432 e/johnd@example.com b/2001-12-12 a/Downtown t/police`
+* `add n/Betsy Crowe e/betsycrowe@example.com p/1234567 b/2002-04-19 a/Upurs Street t/criminal`
-### Locating persons by name: `find`
+#### 3.2.2 Editing friend information: `edit`
-Finds persons whose names contain any of the given keywords.
+Edits a friend's details in TimetaBRO. Use it when they change addresses or phone numbers!
-Format: `find KEYWORD [MORE_KEYWORDS]`
+Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [b/BIRTHDAY] [t/TAG]…​`
-* The search is case-insensitive. e.g `hans` will match `Hans`
-* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans`
-* Only the name is searched.
-* Only full words will be matched e.g. `Han` will not match `Hans`
-* Persons matching at least one keyword will be returned (i.e. `OR` search).
- e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang`
+* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed friend list. The index **must be a positive integer** 1, 2, 3, …​
+* At least one of the optional fields must be provided.
+* Same constraints as add! Meaning no duplicate names. Birthdays also need to be valid dates.
+* Existing values will be updated to the input values.
+* When editing tags, the existing tags of the person will be removed i.e. adding of tags is not cumulative. It's not ideal, but we're working on it!
+* You can remove all the person’s tags by typing `t/` without
+ specifying any tags after it.
+* Click [here](#4-parameters) to find out more about the parameter constraints.
+
+**Successful Command:**
+
+Changes the specified fields of specified friend’s profile.
+
+Output:
+```
+Edited Person {NAME}; Phone: {PHONE}; Email: {EMAIL}; Address: {ADDRESS}; Birthday:
+{BIRTHDAY}; tags: [{TAG}]
+```
+
+**Unsuccessful Command:**\
+Displays an error message:
+```
+Invalid command format!
+edit: Edits the details of the person identified by the index number used in the
+displayed person list. Existing values will be overwritten by the input values.
+Parameters: INDEX (must be a positive integer) [n/NAME] [p/PHONE] [e/EMAIL]
+[a/ADDRESS] [b/BIRTHDAY] [t/TAG]...
+Example: edit 1 p/91234567 e/johndoe@example.com
+```
+If index given is not in the list, the following error message will be returned:
+```
+The person index provided is invalid
+```
Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png)
+* `edit 1 p/91234567 e/johndoe@example.com`: Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively.
+* `edit 2 n/Betsy Crower`: Edits the name of the 2nd person to be `Betsy Crower`.
-### Deleting a person : `delete`
+#### 3.2.3 Deleting a friend's profile: `delete`
-Deletes the specified person from the address book.
+Deletes the specified friend from your TimetaBRO friends list. You may it when your friends leave for SEP and you don't want your friends' list being so cluttered!
Format: `delete INDEX`
-* Deletes the person at the specified `INDEX`.
+* Deletes the person at the specified `INDEX`, in the event they're no longer your friend.
* The index refers to the index number shown in the displayed person list.
-* The index **must be a positive integer** 1, 2, 3, …​
+* The index **must be a positive integer**, e.g. 1, 2, 3, …​
+
+**Successful Command:**
+
+Output:
+```
+[Friend's Name] deleted.
+```
+
+The friend should be removed from the friend's list. Goodbye!
+
+**Unsuccessful Command:**\
+If index is unspecified or not a positive integer, the following error message will be returned:
+```
+Invalid command format!
+delete: Deletes the person identified by the index number used in the displayed
+person list.
+Parameters: INDEX (must be a positive integer)
+Example: delete 1
+```
+
+If index given is not in the list, the following error message will be returned:
+```
+The person index provided is invalid
+```
Examples:
-* `list` followed by `delete 2` deletes the 2nd person in the address book.
+* `list` followed by `delete 2` deletes the 2nd person in the list.
* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command.
-### Clearing all entries : `clear`
+#### 3.2.4 Listing all friends: `list`
+
+Shows a list of all your added friends! You could use it after you use the `find` or `cft` command, which alters your friends' list. Your list now returns to its original state!
+
+Format: `list`
+
+#### 3.2.5 Viewing friend's profile
+
+View your friend's timetable. Now, you can use it compare both your timetables!
+
+![Ui-labelled](images/ui-view-friend.png)
+
Image: Friend profile selection on TimetaBRO
+
+**How to use:**
+1. Scroll down your list of friends, until you locate the friend's timetable you want to see.
+2. Click on the friend's profile. You should see their details appear, on the bottom right panel.
+
+#### 3.2.6 Adding recurring event to friend: `addschedule`
+
+This command adds a weekly recurring event to a friend's schedule, like their modules and CCAs. Use it to keep track of their lesson timings, and CCAs. You could maybe even join in if you're free!
+
+Format: `addschedule INDEX type/TYPE en/EVENT_NAME h/DAY_TIME`
+
+- Adds an event titled `EVENT_NAME` to the specified friend at `INDEX`
+- `TYPE` is a prefix that can either be `module` or `CCA`. Pretty straightforward!
+- Event date and time will be equal to `DAY_TIME`
+ where `DAY_TIME` must be entered in the format `[monday/tuesday/wednesday/thursday/friday/saturday/sunday] HHMM [start time] HHMM [end time]`
+- Event names will be changed to all upper case regardless of whether it was keyed it in lower case or upper case. Imagine TimetaBRO hyping you up!
+- Click [here](#4-parameters) to find out more about the parameter constraints.
+
+**Successful Command:**\
+Input:
+```
+addschedule 1 type/module en/CS2030 h/Monday 1000 1400
+```
+Output:
+```
+New event added:
+Module:
+CS2030 Monday 1000 1400 to [Friend Name]
+```
+
+**Unsuccessful Command:**\
+If you do not put an index or the index is not a positive integer,
+this error message will be shown:
+```
+Invalid index!
+Index can only be 'user' or a positive integer!
+```
+
+If you use an index that is larger than the list,
+this error message will be shown:
+```
+The person index provided is invalid
+Index can be max [list size]!
+```
+
+If you use the wrong format (i.e. missing prefix, wrong event type),
+this error message will be shown:
+```
+[error message]
+Message Usage:
+addschedule: Adds a schedule to the specified contact.
+Parameters: INDEX type/EVENT_TYPE en/EVENT_NAME h/[DAY_OF_WEEK START_TIME [HHMM]
+END_TIME [HHMM]]
+Example: addschedule 1 type/cca en/Basketball h/Monday 1400 1600
+NOTE: If you want to add a cca/module to yourself, use addschedule user
+Example: addschedule user type/cca en/Basketball h/Monday 1400 1600
+```
+
+The error message at the top of the command feedback above will specify what needs to be changed to help you correct any errors you might have made!\
+e.g.\
+Input:
+```
+addschedule
+```
+
+Output:
+```
+Missing prefix(es) for en/ type/ h/ !
+```
+
+
+
+:information_source: Notes about the `addschedule` command:
+
+* Timeslots added of type `module` will be colored blue, while those of type `CCA` will be colored red.
+* To add an event that lasts until the end of the day (midnight), set the end timing as `2400`.
+* You will not be allowed to add an event that clashes with any current or future events.
+
+
+
+#### 3.2.7 Adding non-recurring event to friend: `addevent`
+
+This command adds a dated, non-recurring event to you or your friend's schedule. You could use it to keep track of your friends' important events!
+
+Format: `addevent INDEX en/EVENT_NAME h/DATE_TIME r/REMINDER`
+
+- Adds an event titled `EVENT_NAME` to the specified friend at `INDEX`
+- Event date and time will be equal to `DATE`
+ where `DATE` must be entered in the format `YYYY-MM-DD HHMM [start time] HHMM [end time]`. Date must be a real date too!
+- The reminder feature only works for events in your schedule so far. We're working on it! So for now using `y/n` does not matter for events added to your friends' schedules but it is still necessary!
+- Event names will be changed to all upper case regardless of whether it was keyed it in lower case or upper case.
+- Click [here](#4-parameters) to find out more about the parameter constraints.
+
+**Successful Command:**\
+Input:
+```
+addevent 1 en/CS2030 Finals h/2023-10-31 1000 1400 r/y
+```
+
+Output:
+```
+New event added:
+Dated Event:
+CS2030 FINALS 2023-10-31 1000 1400 to [Friend Name]
+```
+
+**Unsuccessful Command:**
+
+If you do not put an index or the index is not a positive integer,
+this error message will be shown:
+```
+Invalid index!
+Index can only be 'user' or a positive integer!
+```
+
+If you use an index that is larger than the list,
+this error message will be shown:
+```
+The person index provided is invalid
+Index can be max [list size]!
+```
+
+If you use the wrong format (i.e. missing prefix),
+this error message will be shown
+```
+[error message]
+Message Usage:
+addevent: Adds a non-recurring event to the calendar.
+Parameters: INDEX en/EVENT_NAME h/[Date [YYYY-MM-DD] START_TIME [HHMM] END_TIME
+[HHMM]] r/[REMINDER: y/n]
+Example: addevent 1 en/CS2103T Final Exam h/2020-03-02 1400 1600 r/y
+Note: Index should be the index of the friend you are adding the dated event to or
+'user' if you would like to add the event to yourself
+```
+
+The error message at the top of the command feedback above will specify what needs to be rectified.\
+e.g.\
+Input:
+```
+addevent 1
+```
+
+Output:
+```
+Missing prefix(es) for en/ h/ r/ !
+```
+
+
+
+:information_source: Notes about the `addevent` command:
+
+* Timeslots added with this command will be green.
+* To add an event that lasts until the end of the day (midnight), set the end timing as `2400`.
+* You will not be allowed to add an event that clashes with any current or future events. Tell your friends if they have clashes!
+
+
+
+#### 3.2.8 Deleting recurring event from friend: `rmschedule`
+
+Removes the specified recurring item from the specified Person's schedule. You might find it useful when they have a change in schedule! If there are multiple recurring events with the same names, this command will the instance of the event that was added the earliest!
+
+Format: `rmschedule INDEX type/EVENT_TYPE en/EVENT_NAME`
+
+- Removes an event titled `EVENT_NAME` from the specified friend at `INDEX`
+- `EVENT_TYPE` is a prefix that can either be `module` or `CCA`. Pretty straightforward!
+- `EVENT_NAME` must exist in the friend's schedule.
+- Click [here](#4-parameters) to find out more about the parameter constraints.
+
+**Successful commands:**\
+Input:
+```
+rmschedule 1 type/CCA en/Basketball
+```
+
+Output:
+```
+BASKETBALL has been removed from [Friend's Name]!
+```
+
+**Unsuccessful commands:**\
+If you do not put an index or the index is not a positive integer,
+this error message will be shown:
+```
+Invalid index!
+Index can only be 'user' or a positive integer!
+```
+
+If you use an index that is larger than the list,
+this error message will be shown:
+```
+The person index provided is invalid
+Index can be max [list size]!
+```
+
+If given event does not exist, this error message will be shown:
+```
+[TYPE] [EVENT_NAME] does not exist!
+Please check that you have entered the correct [TYPE] name!
+```
+
+If wrong command format is used (i.e. missing prefixes):
+```
+[error message]
+Message Usage:
+rmschedule: Removes an event from the specified contact's calendar.
+Parameters: INDEX type/EVENT_TYPE en/EVENT_NAME
+Example: rmschedule 1 type/cca en/Basketball
+NOTE: If you want to remove an event from yourself, use index user
+Example: rmschedule user type/cca en/Basketball
+```
+
+The error message at the top of the command feedback above will specify what needs to be rectified.\
+e.g.\
+Input:
+```
+rmschedule 1
+```
+
+Output:
+```
+Missing prefix(es) for en/ type/ !
+```
+
+
+
+:information_source: Notes about the `rmschedule` and `rmevent` command:
+
+If there are multiple time slots with the same names, the command will remove time slots in the chronological order they were added.
+
+
+
+#### 3.2.9 Deleting non-recurring event from friend: `rmevent`
+
+Removes the specified event from the specified Person. Use this if there are a change in anyone's plans!
+If there are multiple non-recurring events with the same names, this command will the instance of the event that was added the earliest!
+
+Format: `rmevent INDEX en/EVENT_NAME`
+
+- Removes an event titled `EVENT_NAME` from the specified friend at `INDEX`
+- `EVENT_NAME` must exist in the friend's schedule.
+- Click [here](#4-parameters) to find out more about the parameter constraints.
+
+**Successful commands:**\
+Input:
+```
+rmevent 1 en/CS2103T Final Exam
+```
+
+Output:
+```
+Dated event 'CS2103T FINAL EXAM' deleted from [Friend's Name]'s calendar!
+```
+
+**Unsuccessful commands:**\
+If you do not put an index or the index is not a positive integer,
+this error message will be shown:
+```
+Invalid index!
+Index can only be 'user' or a positive integer!
+```
+
+If you use an index that is larger than the list,
+this error message will be shown:
+```
+The person index provided is invalid
+Index can be max [list size]!
+```
+
+If given event does not exist, this error message will be shown:
+```
+Event [EVENT_NAME] does not exist!
+Please check that you have entered the correct event name!
+```
+
+If wrong command format is used (i.e. missing prefix):
+```
+Missing prefix(es) for en/ !
+Message Usage:
+rmevent: Removes an event from the specified contact's calendar.
+Parameters: INDEX en/EVENT_NAME
+Example: rmevent 1 en/CS2103T Final Exam
+NOTE: If you want to remove an event from yourself, use rmevent user
+Example: rmevent user en/CS2103T Final Exam
+```
+
+#### 3.2.10 Finding common free times with your friend(s): `cft`
+
+Finds friend(s) with the same free times as you, or returns a message if no friends are free. This command is for you to look for lunch buddies or to see what time you can bug them!
+
+Format:
+
+**Finding common free times with a specific friend**: `cft INDEX`
+
+* Finds common free times with friend of the specified `INDEX`.
+
+**Finding common free times with entire friends list**: `cft`
+
+* Finds common free times with friend of the specified `INDEX`. Use it with your best friend!
+* Finds common free times with **all friends** in the list if `INDEX` is not included. Now find someone to bug!
+
+**Successful Command:**\
+Input:
+```
+cft
+```
+
+Output:\
+Displays all the common free times you have with all your friends in your friend list.
+
+```
+Here are the contacts with the same free time as you:
+You and Bernice Yu have no common free time!
+You and Charlotte have no common free time!
+You have common free times with Alex Yeoh at:
+[Monday 0000 0800]
+[Monday 2000 2400]
+[Tuesday 0000 1300]
+[Wednesday 0000 1200]
+[Wednesday 1800 2400]
+[Thursday 0000 2400]
+[Friday 0000 2400]
+[Saturday 0000 2400]
+[Sunday 0000 2400]
+```
+
+Input:
+```
+cft 1
+```
+
+Output:
+```
+You have common free times with Alex Yeoh at:
+[Monday 0000 0800]
+[Monday 2000 2400]
+[Tuesday 0000 1300]
+[Wednesday 0000 1200]
+[Wednesday 1800 2400]
+[Thursday 0000 2400]
+[Friday 0000 2400]
+[Saturday 0000 2400]
+[Sunday 0000 2400]
+```
+
+If your contact is a hustler, and you have no common free times with a contact, it will display:
+```
+You and [Friend's Name] have no common free time!
+```
+
+If you're super busy and your entire timetable is full for the whole week, it will return:
+```
+You have no free time!
+```
+
+If you execute `cft` and you have no common free times with any contact, it will return:
+```
+You have no contacts with the same free time as you!
+```
+
+**Unsuccessful Command:**
+
+If you input invalid command format, the app will display:
+```
+Invalid command format!
+cft: Finds all contacts with the same free time as the User.
+Example: cft
+```
+
+If you input an index that does not exist in the list, the app will display:
+```
+The person index provided is invalid
+```
+
+Examples:
+* `cft` lists all friends .
+* `cft 1` finds the 1st person in the TimetaBRO friend list and displays the common free times you have with the person.
+
+### 3.3 Reminder Commands
+
+#### 3.3.1 Set reminder for non-recurring events: `setReminder`
+
+Sets a reminder for a **dated, non-recurring event** in your schedule. Use it when you forgot to set reminders when adding it, or decide that an event is important enough for a reminder afterwards. You'll never forget it now!
+
+Format: `setReminder EVENT_NAME`
+- Set reminder for an event titled `EVENT_NAME` in your schedule!
+- Click [here](#4-parameters) to find out more about the parameter constraints.
+
+**Successful command:**\
+Input:
+```
+setReminder CS2103T Final Exam
+```
+
+Output:
+```
+Reminder set for following event:
+CS2103T FINAL EXAM
+```
+
+**Unsuccessful command:**
+
+If an invalid event name is used, it will return:
+```
+No such event exists!
+```
+
+#### 3.3.2 Remove reminder for non-recurring events: `rmReminder`
-Clears all entries from the address book.
+Removes a reminder for a dated event from your schedule. You may find this command great when you accidentally set a reminder, or decided that it's not that important!
+
+Format: `rmReminder EVENT_NAME`
+- Remove reminder for an event titled `EVENT_NAME` in your schedule.
+- Click [here](#4-parameters) to find out more about the parameter constraints.
+
+**Successful command:**\
+Input:
+```
+rmReminder CS2103T Final Exam
+```
+
+Output:
+```
+Reminder removed for following event:
+CS2103T FINAL EXAM
+```
+
+**Unsuccessful command:**\
+If an invalid event name is used, it will return:
+```
+No such event exists!
+```
+
+### 3.4 Other Commands
+
+#### 3.4.1 Viewing help: `help`
+
+Shows a message explaining how to access the help page. We promise it's helpful.
+
+Format: `help`
+
+The app should provide you with a pop-up as shown below!
+![Ui-Labelled](images/HelpPopUp.png)
+
+#### 3.4.2 Clearing all entries: `clear`
+
+Clears all entries from TimetaBRO. You can use it to clear the sample data we provided, or if you find it too cluttered. Be careful not to delete everything by accident!
Format: `clear`
-### Exiting the program : `exit`
+**Successful Command:**\
+Output:
+```
+All friends have been deleted.
+```
-Exits the program.
+The entire friend list should be emptied. Start afresh!
-Format: `exit`
+#### 3.4.3 Exiting the program: `exit`
-### Saving the data
+Exits the program. Use it instead of the traditional button if you're already typing!
+
+Format: `exit`
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+The app closes after saving all data.
-### Editing the data file
+--------------------------------------------------------------------------------------------------------------------
-AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file.
+## **4. Parameters**
-
:exclamation: **Caution:**
-If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
+
Caution:
+Command parameter inputs have constraints to ensure their use is streamlined.
-### Archiving data files `[coming in v2.0]`
+| Parameter | Constraints |
+|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **NAME** | Alphanumeric, any length. |
+| **PHONE** | 8 digits. |
+| **INDEX** | A number present on the list of friends in the address book. |
+| **ADDRESS** | No constraints. |
+| **EMAIL** | Emails should be of the format `local-part@domain` and adhere to the following constraints: 1. The local-part should only contain alphanumeric characters and these special characters, excluding the parentheses, (+_.-). The local-part may not start or end with any special characters. 2. This is followed by a '@' and then a domain name. The domain name is made up of domain labels separated by periods. The domain name must: - end with a domain label at least 2 characters long - have each domain label start and end with alphanumeric characters - have each domain label consist of alphanumeric characters, separated only by hyphens, if any. |
+| **BIRTHDAY** | `YYYY-MM-DD`, a valid date. |
+| **DAY_TIME** | `DAY HHMM HHMM` `DAY` is any day of the week fully spelt out (case insensitive), the first `HHMM` is the start time of the time block and the second is the end time, `HHMM` is a 24 hour time format of half hour intervals, from `0000` to `2400`. Example: `mOndaY 2330 2400` |
+| **DATE_TIME** | `YYYY-MM-DD HHMM HHMM` `YYYY-MM-DD` is the date of the event which has to be valid, the first `HHMM` is the start time of the time block and the second is the end time, `HHMM` is a 24 hour time format of half hour intervals, from `0000` to `2400`. Example: `2023-11-08 2330 2400` |
+| **EVENT_NAME (MODULE)** | Accepts any NUS module code. Example: `CS2103T`/`UTC2113`/`MA1521` |
+| **EVENT_NAME (CCA)** | Alphanumeric, any length. |
+| **EVENT_NAME** | Alphanumeric, any length. |
+| **EVENT_TYPE** | `cca` or `module` only. |
+| **REMINDER** | `y` to turn reminder on. `n` to turn reminder off. |
-_Details coming soon ..._
--------------------------------------------------------------------------------------------------------------------
-## FAQ
+## **5. Saving the data**
+
+TimetaBRO data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually!
+
+## **6. Editing the data file**
-**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder.
+TimetaBRO data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file!
+
+
Caution:
+If your changes to the data file makes its format invalid, TimetaBRO will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
+
--------------------------------------------------------------------------------------------------------------------
-## Known issues
+## **7. FAQ**
+
+**How do I transfer my data to another Computer?**
+Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous TimetaBRO home folder.
+
+**Is TimetaBRO compatible with Mac and Linux operating systems, or is it Windows-specific?**
+TimetaBRO is compatible with multiple operating systems, including Windows, Mac, and Linux! It runs on systems that support Java 11, so you can use it on your preferred platform.
+
+**Can I import my friend's schedule from a different calendar application into TimetaBRO?**
+Nope! TimetaBRO does not offer a direct import feature for schedules from other calendar applications. You'll need to manually add your friends' schedules to TimetaBRO using the add command. Tedious, but we're on it!
+
+**Is there a way to set recurring events for specific dates or weekdays, like every Tuesday, without manually adding them one by one?**
+Yes! You can add recurring events for specific weekdays in TimetaBRO using the `addschedule` command. This feature allows you to set events for particular days of the week, making it easier to input recurring events.
+
+**What happens if I accidentally delete a friend or event in TimetaBRO? Is there a way to recover deleted data?**
+Sorry! TimetaBRO does not have a built-in data recovery feature. When you delete a friend or event, the data is permanently removed from the application. Do double-check before deleting something, or clearing your app!
+
+
+**How can I customize the reminder settings for events added to TimetaBRO?**
+You can customize the reminder settings for events when adding them using the `addevent` command. The r/y or r/n option allows you to enable or disable reminders for specific events.
+
+**Is there a way to share my TimetaBRO schedule with others or export it to a different format, such as a calendar file?**
+Nope! TimetaBRO currently does not support sharing schedules with others or exporting them to external formats. It primarily functions as a personal scheduling tool.
+
+**Can I remove a specific event or schedule from my timetable or my friends' timetable?**
+Not yet! However, for now, you may set slightly different names for events with the same name, such as 'Meetup with Jason (1)' and 'Meetup with Jason (2)' so that you can select the specific event to delete later. If not, when you try to remove an event from the schedule, it will remove the one that you added first!
+
+**If I have two events that overlap, can I add both of them to the calendar?**
+Nope! As TimetaBRO was designed to be a timetable management app, we would not be allowing overlapping events to be added into the timetable. You don't wanna double book your own time!
+
+**Why do my event names automatically change to all capitalised letters?**
+We want to standardize the format of all event names to prevent users from accidentally adding multiple events wit the same names but in different formats! (i.e. only first letter is capitalised vs only last letter is capitalised)
+
+**Why can't I put 0000 instead of 2400 when my event ends at 12AM?**
+This is because it makes it hard for TimetaBRO to understand that it ends at 12AM the next day! Hence, 2400 is a workaround for this. Stay tuned for enhancements!
+
+--------------------------------------------------------------------------------------------------------------------
+
+## **8. Known issues**
+
+1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen :( But don't worry! The remedy is to delete the `preferences.json` file created by the application before running the application again! Simple!
+
+2. **The timetable only supports timings in 30-minute gaps** because this is an app made for NUS students! Since NUS timetables are set in 30-minute gaps, we have adapted this to better fit the NUS timetable style.
+
+3. While you **can't select a specific event to delete**, don't sweat it. We've got you covered. The events are removed in the order they were added, following a first-in-first-out basis. So, the earliest added event will be the first to bid farewell!
+
+4. You can currently put birthdays in the future. Not to worry if you accidentally mistype your friends birthday! You use the edit command to rectify any future birthdays.
-1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again.
+If you have any more questions, feel free to ask!
--------------------------------------------------------------------------------------------------------------------
-## Command summary
-
-Action | Format, Examples
---------|------------------
-**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague`
-**Clear** | `clear`
-**Delete** | `delete INDEX` e.g., `delete 3`
-**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` e.g.,`edit 2 n/James Lee e/jameslee@example.com`
-**Find** | `find KEYWORD [MORE_KEYWORDS]` e.g., `find James Jake`
-**List** | `list`
-**Help** | `help`
+## **9. Command Summary**
+For you TLDR-ers!
+
+| Action | Format and Examples |
+|---------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Add a Friend** | `add n/NAME p/PHONE e/EMAIL a/ADDRESS b/BIRTHDAY [t/TAG]…​` Example: `add n/John Doe p/98765432 e/johnd@example.com a/1 Hon Sui Sen Dr, Singapore 117588 b/2023-11-05 t/police` |
+| **Add a Dated Event to friend** | `addevent INDEX en/EVENT NAME h/DATE r/REMINDER` Example: `addevent 1 en/CS2030 Finals h/2023-10-31 1000 1400 r/y` |
+| **Add a Dated Event to user** | `addevent user en/EVENT NAME h/DATE r/REMINDER` Example: `addevent user en/CS2030 Finals h/2023-10-31 1000 1400 r/y` |
+| **Remove an Event from friend** | `rmevent INDEX en/EVENT NAME` Example: `rmevent 1 en/CS2103T Lecture` |
+| **Remove an Event from user** | `rmevent user en/EVENT NAME` Example: `rmevent user en/CS2103T Lecture` |
+| **Add a Recurring Event to friend** | `addschedule INDEX type/EVENT_TYPE en/EVENT_NAME h/DAY_TIME` Example: `addschedule 1 type/module en/CS2030 h/Monday 1000 1400` |
+| **Add a Recurring Event to user** | `addschedule user type/EVENT_TYPE en/EVENT_NAME h/DAY_TIME` Example: `addschedule 1 type/module en/CS2030 h/Monday 1000 1400` |
+| **Remove a Recurring Event from friend** | `rmschedule INDEX type/EVENT_TYPE en/EVENT_NAME` Example: `rmschedule 1 type/CCA en/CS2103T Lecture` |
+| **Remove a Recurring Event from user** | `rmschedule user type/EVENT_TYPE en/EVENT_NAME` Example: `rmschedule 1 type/CCA en/CS2103T Lecture` |
+| **Remove a Reminder** | `rmReminder EVENT_NAME` Example: `rmReminder CS2103T Lecture` |
+| **Set a Reminder** | `setReminder EVENT_NAME` Example: `setReminder CS2103T Lecture` |
+| **Clear All Entries** | `clear` |
+| **Delete a Friend** | `delete INDEX` Example: `delete 3` |
+| **Edit Friend Info** | `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [b/BIRTHDAY] [t/TAG]…​` Example: `edit 2 n/James Lee e/jameslee@example.com` |
+| **Edit User Info** | `user [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [b/BIRTHDAY] [t/TAG]…​` Example: `user n/James Lee e/jameslee@example.com` |
+| **List All Friends** | `list` |
+| **View Help** | `help` |
+| **Find Common Free Times with friend** | `cft INDEX` Example: `cft 1` |
+| **Find Common Free Times with all friends** | `cft` |
+| **Exit** | `exit` |
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..f157bc08b5a 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "TimetaBRO"
theme: minima
header_pages:
@@ -8,7 +8,7 @@ header_pages:
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "AY2324S1-CS2103T-W12-4/tp"
github_icon: "images/github-icon.png"
plugins:
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 0d3f6e80ced..8631025b15f 100644
--- a/docs/_sass/minima/_base.scss
+++ b/docs/_sass/minima/_base.scss
@@ -288,7 +288,7 @@ table {
text-align: center;
}
.site-header:before {
- content: "AB-3";
+ content: "TimetaBRO";
font-size: 32px;
}
}
diff --git a/docs/diagrams/AddScheduleSequenceDiagram.puml b/docs/diagrams/AddScheduleSequenceDiagram.puml
new file mode 100644
index 00000000000..8b92d784ab3
--- /dev/null
+++ b/docs/diagrams/AddScheduleSequenceDiagram.puml
@@ -0,0 +1,70 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":AddScheduleCommandParser" as AddScheduleCommandParser LOGIC_COLOR
+participant "d:AddScheduleCommand" as AddScheduleCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("addschedule user...")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("addschedule user...")
+activate AddressBookParser
+
+create AddScheduleCommandParser
+AddressBookParser -> AddScheduleCommandParser
+activate AddScheduleCommandParser
+
+AddScheduleCommandParser --> AddressBookParser
+deactivate AddScheduleCommandParser
+
+AddressBookParser -> AddScheduleCommandParser : parse("user type/cca...")
+activate AddScheduleCommandParser
+
+create AddScheduleCommand
+AddScheduleCommandParser -> AddScheduleCommand
+activate AddScheduleCommand
+
+AddScheduleCommand --> AddScheduleCommandParser : addCca
+deactivate AddScheduleCommand
+
+AddScheduleCommandParser --> AddressBookParser : addCca
+deactivate AddScheduleCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+AddScheduleCommandParser -[hidden]-> AddressBookParser
+destroy AddScheduleCommandParser
+
+AddressBookParser --> LogicManager : d
+deactivate AddressBookParser
+
+LogicManager -> AddScheduleCommand : execute()
+activate AddScheduleCommand
+
+AddScheduleCommand -> Model : addCca(table tennis monday 1400 1600)
+activate Model
+
+Model --> AddScheduleCommand
+deactivate Model
+
+create CommandResult
+AddScheduleCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> AddScheduleCommand
+deactivate CommandResult
+
+AddScheduleCommand --> LogicManager : result
+deactivate AddScheduleCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/ClickToViewActivityDiagram.puml b/docs/diagrams/ClickToViewActivityDiagram.puml
new file mode 100644
index 00000000000..30e89b85b81
--- /dev/null
+++ b/docs/diagrams/ClickToViewActivityDiagram.puml
@@ -0,0 +1,12 @@
+@startuml
+
+start
+: onMouseClicked event triggered when a cell of the friend list is clicked on;
+: PersonListPanel.PersonListViewCell#updateItem() fires new ListCellSelectedEvent with the selected Person;
+: MainWindow#fillInnerParts() retrieves selected Person object with ListCellSelectedEvent#getSelectedPerson();
+: Create new SelectedFriendCard with the Person;
+: Store the SelectedFriendCard in friendProfile;
+: Replace contents of SelectedFriendPlaceHolder with friendProfile;
+stop
+
+@enduml
diff --git a/docs/diagrams/EditUserExecuteSequenceDiagram.puml b/docs/diagrams/EditUserExecuteSequenceDiagram.puml
new file mode 100644
index 00000000000..45835f675e9
--- /dev/null
+++ b/docs/diagrams/EditUserExecuteSequenceDiagram.puml
@@ -0,0 +1,48 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+mainframe Execute Command
+
+box Logic LOGIC_COLOR_T1
+participant ":EditUserCommand" as EditUserCommand LOGIC_COLOR
+participant "result:CommandResult" as CommandResult LOGIC_COLOR
+participant "editedUser:User" as User LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+activate EditUserCommand
+EditUserCommand -> Model : getUser()
+activate Model
+
+Model --> EditUserCommand : user
+deactivate Model
+
+create User
+EditUserCommand -> User : createEditedUser(user, editUserDescriptor)
+activate User
+
+User --> EditUserCommand : editedUser
+deactivate User
+
+EditUserCommand -> Model : setUser(editedUser)
+activate Model
+
+
+
+Model --> EditUserCommand :
+deactivate Model
+
+create CommandResult
+EditUserCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> EditUserCommand
+deactivate CommandResult
+
+<-- EditUserCommand : result
+
+@enduml
diff --git a/docs/diagrams/EditUserSequenceDiagram.puml b/docs/diagrams/EditUserSequenceDiagram.puml
new file mode 100644
index 00000000000..702522d493b
--- /dev/null
+++ b/docs/diagrams/EditUserSequenceDiagram.puml
@@ -0,0 +1,66 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+skinparam backgroundColor<> #FFFFFF
+skinparam styleName<> frame
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":EditUserCommandParser" as EditUserCommandParser LOGIC_COLOR
+participant ":EditUserDescriptor" as EditUserDescriptor LOGIC_COLOR
+participant "e:EditUserCommand" as EditUserCommand LOGIC_COLOR
+end box
+
+[-> LogicManager : execute()
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand()
+activate AddressBookParser
+
+create EditUserCommandParser
+AddressBookParser -> EditUserCommandParser
+activate EditUserCommandParser
+
+EditUserCommandParser --> AddressBookParser
+deactivate EditUserCommandParser
+
+AddressBookParser -> EditUserCommandParser : parse()
+activate EditUserCommandParser
+
+create EditUserDescriptor
+EditUserCommandParser -> EditUserDescriptor :
+activate EditUserDescriptor
+
+EditUserDescriptor --> EditUserCommandParser :
+deactivate EditUserDescriptor
+
+EditUserCommandParser -> EditUserDescriptor : setName()
+activate EditUserDescriptor
+
+EditUserDescriptor --> EditUserCommandParser :
+deactivate EditUserDescriptor
+
+create EditUserCommand
+EditUserCommandParser -> EditUserCommand :
+activate EditUserCommand
+
+EditUserCommand --> EditUserCommandParser : e
+deactivate EditUserCommand
+
+EditUserCommandParser --> AddressBookParser : e
+deactivate EditUserCommandParser
+
+AddressBookParser --> LogicManager : e
+deactivate AddressBookParser
+
+LogicManager -> EditUserCommand : execute(model)
+activate EditUserCommand
+ref over EditUserCommand : Execute Command
+
+EditUserCommand --> LogicManager : result
+deactivate EditUserCommand
+
+<-- LogicManager
+@enduml
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index 0de5673070d..eba4dc2d12b 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -6,11 +6,9 @@ skinparam classBackgroundColor MODEL_COLOR
Package Model as ModelPackage <>{
Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook
-Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs
Class "<>\nModel" as Model
Class AddressBook
Class ModelManager
-Class UserPrefs
Class UniquePersonList
Class Person
@@ -20,6 +18,18 @@ Class Name
Class Phone
Class Tag
+package user as "User" {
+Class "<>\nReadOnlyUserData" as ReadOnlyUserData
+Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs
+Class UserPrefs
+Class UserData
+Class User
+}
+
+package Timetable {
+Class Hidden #FFFFFF
+}
+
Class I #FFFFFF
}
@@ -30,11 +40,15 @@ AddressBook .up.|> ReadOnlyAddressBook
ModelManager .up.|> Model
Model .right.> ReadOnlyUserPrefs
+Model .right.> ReadOnlyUserData
Model .left.> ReadOnlyAddressBook
ModelManager -left-> "1" AddressBook
ModelManager -right-> "1" UserPrefs
+ModelManager -right-> "1" UserData
UserPrefs .up.|> ReadOnlyUserPrefs
+UserData .up.|> ReadOnlyUserData
+UserData --> "1" User
AddressBook *--> "1" UniquePersonList
UniquePersonList --> "~* all" Person
Person *--> Name
@@ -42,13 +56,17 @@ Person *--> Phone
Person *--> Email
Person *--> Address
Person *--> "*" Tag
+Person -down-> "Timetable"
+User -down-> "Timetable"
+
+
+User --> Person
Person -[hidden]up--> I
UniquePersonList -[hidden]right-> I
Name -[hidden]right-> Phone
Phone -[hidden]right-> Address
-Address -[hidden]right-> Email
ModelManager --> "~* filtered" Person
@enduml
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index a821e06458c..6dc0f87ca2e 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -11,6 +11,13 @@ Class "<>\nUserPrefsStorage" as UserPrefsStorage
Class JsonUserPrefsStorage
}
+package "UserData Storage" {
+Class "<>\nUserDataStorage" as UserDataStorage
+Class JsonUserDataStorage
+Class JsonSerializableUserData
+Class JsonAdaptedUser
+}
+
Class "<>\nStorage" as Storage
Class StorageManager
@@ -20,8 +27,11 @@ Class JsonAddressBookStorage
Class JsonSerializableAddressBook
Class JsonAdaptedPerson
Class JsonAdaptedTag
-}
+
+}
+package "Timetable Classes"{
+}
}
Class HiddenOutside #FFFFFF
@@ -29,15 +39,22 @@ HiddenOutside ..> Storage
StorageManager .up.|> Storage
StorageManager -up-> "1" UserPrefsStorage
+StorageManager -up-> "1" UserDataStorage
StorageManager -up-> "1" AddressBookStorage
Storage -left-|> UserPrefsStorage
Storage -right-|> AddressBookStorage
+Storage -right-|> UserDataStorage
JsonUserPrefsStorage .up.|> UserPrefsStorage
+JsonUserDataStorage .up.|> UserDataStorage
JsonAddressBookStorage .up.|> AddressBookStorage
+JsonUserDataStorage ..> JsonSerializableUserData
JsonAddressBookStorage ..> JsonSerializableAddressBook
+JsonSerializableUserData --> "1" JsonAdaptedUser
JsonSerializableAddressBook --> "*" JsonAdaptedPerson
JsonAdaptedPerson --> "*" JsonAdaptedTag
+JsonAdaptedPerson --> "Timetable Classes"
+JsonAdaptedUser --> "Timetable Classes"
@enduml
diff --git a/docs/diagrams/TimetableClassDiagram.puml b/docs/diagrams/TimetableClassDiagram.puml
new file mode 100644
index 00000000000..feaea78bbc8
--- /dev/null
+++ b/docs/diagrams/TimetableClassDiagram.puml
@@ -0,0 +1,49 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+
+Class Person
+Class User
+
+
+package timetable as "Timetable" {
+Class Schedule
+Class TimeBlock
+Class HalfHourBlocks
+Class DatedEvent
+Class FreeTime
+Class Module
+Class Cca
+}
+
+Class I #FFFFFF
+
+
+'Class HiddenOutside #FFFFFF
+'HiddenOutside ..> Model
+
+Person *--> "1" Schedule
+
+FreeTime *--> "1" HalfHourBlocks
+TimeBlock *--> "1" HalfHourBlocks
+
+Schedule *--> "*" DatedEvent
+Schedule *--> "*" Module
+Schedule *--> "*" Cca
+TimeBlock <|-- Cca
+TimeBlock <|-- Module
+TimeBlock <|-- DatedEvent
+Schedule -- FreeTime
+
+User *--> "*" DatedEvent
+
+Person -[hidden]down-> Schedule
+User -[hidden]right-> Person
+
+FreeTime -[hidden]left-> DatedEvent
+Schedule -[hidden]down--> TimeBlock
+
+@enduml
diff --git a/docs/diagrams/TimetableProblemDomain.puml b/docs/diagrams/TimetableProblemDomain.puml
new file mode 100644
index 00000000000..1590c67a445
--- /dev/null
+++ b/docs/diagrams/TimetableProblemDomain.puml
@@ -0,0 +1,39 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+' Define classes
+class Person
+class Friend
+class User
+class Schedule
+class FreeTime
+class TimeBlock
+class Interval
+class Day
+class Name
+class DatedEvent
+class Cca
+class Module
+class Reminder
+
+' Define relationships
+Person <|-left- Friend
+Person <|-left- User
+
+Person *--"1" Schedule
+Schedule *-- "*" TimeBlock : contains
+Schedule .left.> FreeTime
+FreeTime -- Interval
+FreeTime -- Day
+TimeBlock -left- "1" Interval
+TimeBlock -left- "1" Day
+TimeBlock -left- "1" Name
+TimeBlock <|-- DatedEvent
+TimeBlock <|-- Cca
+TimeBlock <|-- Module
+DatedEvent -- "0..1" Reminder
+
+@enduml
diff --git a/docs/diagrams/TimetableSolutionDomain.puml b/docs/diagrams/TimetableSolutionDomain.puml
new file mode 100644
index 00000000000..42d923bf605
--- /dev/null
+++ b/docs/diagrams/TimetableSolutionDomain.puml
@@ -0,0 +1,61 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+
+Class Person
+Class User
+
+
+package timetable as "Timetable" {
+Class Schedule
+Class TimeBlock
+Class HalfHourBlocks
+Class DatedEvent
+Class FreeTime
+Class Module
+Class Cca
+}
+
+package Logic <> {
+Class HiddenLogic #FFFFFF
+}
+
+package Ui <> {
+Class HiddenUi #FFFFFF
+}
+
+Class I #FFFFFF
+
+
+'Class HiddenOutside #FFFFFF
+'HiddenOutside ..> Model
+
+Person *--> "1" Schedule
+
+FreeTime *--> "1" HalfHourBlocks
+TimeBlock *--> "1" HalfHourBlocks
+
+Schedule *--> "*" DatedEvent
+Schedule *--> "*" Module
+Schedule *--> "*" Cca
+TimeBlock <|-- Cca
+TimeBlock <|-- Module
+TimeBlock <|-- DatedEvent
+Schedule -- FreeTime
+
+User *--> "*" DatedEvent
+
+Person -[hidden]down-> Schedule
+User -[hidden]right-> Person
+
+FreeTime -[hidden]left-> DatedEvent
+Schedule -[hidden]down--> TimeBlock
+
+Logic -right-> Schedule
+Ui -right-> Schedule
+Ui -[hidden]up- Logic
+
+@enduml
diff --git a/docs/diagrams/TimetableStorageClassDiagram.puml b/docs/diagrams/TimetableStorageClassDiagram.puml
new file mode 100644
index 00000000000..8cbae8d81f3
--- /dev/null
+++ b/docs/diagrams/TimetableStorageClassDiagram.puml
@@ -0,0 +1,22 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor STORAGE_COLOR
+skinparam classBackgroundColor STORAGE_COLOR
+
+Class JsonAdaptedPerson
+Class JsonAdaptedUser
+
+package "Timetable Classes"{
+Class JsonAdaptedSchedule
+Class JsonAdaptedModule
+Class JsonAdaptedCca
+Class JsonAdaptedDatedEvent
+
+JsonAdaptedSchedule *--> "*" JsonAdaptedModule
+JsonAdaptedSchedule *--> "*" JsonAdaptedCca
+JsonAdaptedSchedule *--> "*" JsonAdaptedDatedEvent
+}
+
+JsonAdaptedPerson *--> "1" JsonAdaptedSchedule
+JsonAdaptedUser *--> "1" JsonAdaptedSchedule
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 95473d5aa19..38b0aaeca30 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -15,6 +15,9 @@ Class PersonListPanel
Class PersonCard
Class StatusBarFooter
Class CommandBox
+Class UserCard
+Class SelectedFriendCard
+Class Reminder
}
package Model <> {
@@ -34,6 +37,8 @@ MainWindow *-down-> "1" CommandBox
MainWindow *-down-> "1" ResultDisplay
MainWindow *-down-> "1" PersonListPanel
MainWindow *-down-> "1" StatusBarFooter
+MainWindow *-down-> "1" UserCard
+MainWindow *-down-> "1" SelectedFriendCard
MainWindow --> "0..1" HelpWindow
PersonListPanel -down-> "*" PersonCard
@@ -45,16 +50,21 @@ CommandBox --|> UiPart
PersonListPanel --|> UiPart
PersonCard --|> UiPart
StatusBarFooter --|> UiPart
+UserCard --|> UiPart
HelpWindow --|> UiPart
+SelectedFriendCard --|> UiPart
+
PersonCard ..> Model
UiManager -right-> Logic
MainWindow -left-> Logic
PersonListPanel -[hidden]left- HelpWindow
+UserCard -[hidden]left- HelpWindow
HelpWindow -[hidden]left- CommandBox
CommandBox -[hidden]left- ResultDisplay
ResultDisplay -[hidden]left- StatusBarFooter
+ResultDisplay -[hidden]left- SelectedFriendCard
MainWindow -[hidden]-|> UiPart
@enduml
diff --git a/docs/images/AddScheduleSequenceDiagram.png b/docs/images/AddScheduleSequenceDiagram.png
new file mode 100644
index 00000000000..77136045665
Binary files /dev/null and b/docs/images/AddScheduleSequenceDiagram.png differ
diff --git a/docs/images/ClickToViewActivityDiagram.png b/docs/images/ClickToViewActivityDiagram.png
new file mode 100644
index 00000000000..549a4743449
Binary files /dev/null and b/docs/images/ClickToViewActivityDiagram.png differ
diff --git a/docs/images/EditUserExecuteSequenceDiagram.png b/docs/images/EditUserExecuteSequenceDiagram.png
new file mode 100644
index 00000000000..2d438ef8f60
Binary files /dev/null and b/docs/images/EditUserExecuteSequenceDiagram.png differ
diff --git a/docs/images/EditUserSequenceDiagram.png b/docs/images/EditUserSequenceDiagram.png
new file mode 100644
index 00000000000..c1adb2b44f0
Binary files /dev/null and b/docs/images/EditUserSequenceDiagram.png differ
diff --git a/docs/images/HelpPopUp.png b/docs/images/HelpPopUp.png
new file mode 100644
index 00000000000..b91d952610c
Binary files /dev/null and b/docs/images/HelpPopUp.png differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index a19fb1b4ac8..259e1c2f50b 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/Reminder-window.png b/docs/images/Reminder-window.png
new file mode 100644
index 00000000000..d2d4dcb08e6
Binary files /dev/null and b/docs/images/Reminder-window.png differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index 18fa4d0d51f..37f1c6ec999 100644
Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ
diff --git a/docs/images/TimetableClassDiagram.png b/docs/images/TimetableClassDiagram.png
new file mode 100644
index 00000000000..06e800479e0
Binary files /dev/null and b/docs/images/TimetableClassDiagram.png differ
diff --git a/docs/images/TimetableProblemDomain.png b/docs/images/TimetableProblemDomain.png
new file mode 100644
index 00000000000..aebc55ecc00
Binary files /dev/null and b/docs/images/TimetableProblemDomain.png differ
diff --git a/docs/images/TimetableSolutionDomain.png b/docs/images/TimetableSolutionDomain.png
new file mode 100644
index 00000000000..ec3856c16e7
Binary files /dev/null and b/docs/images/TimetableSolutionDomain.png differ
diff --git a/docs/images/TimetableStorageClassDiagram.png b/docs/images/TimetableStorageClassDiagram.png
new file mode 100644
index 00000000000..b5c5c6d2bef
Binary files /dev/null and b/docs/images/TimetableStorageClassDiagram.png differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 5bd77847aa2..a6b2a6c3aa4 100644
Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ
diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png
index 11f06d68671..e9cf370d9de 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/andrefoo.png b/docs/images/andrefoo.png
new file mode 100644
index 00000000000..f8d04be1bba
Binary files /dev/null and b/docs/images/andrefoo.png differ
diff --git a/docs/images/kristayeo.png b/docs/images/kristayeo.png
new file mode 100644
index 00000000000..3e32405ef73
Binary files /dev/null and b/docs/images/kristayeo.png differ
diff --git a/docs/images/lululwtv.png b/docs/images/lululwtv.png
new file mode 100644
index 00000000000..f1a5877a7d6
Binary files /dev/null and b/docs/images/lululwtv.png differ
diff --git a/docs/images/owenyeo.png b/docs/images/owenyeo.png
new file mode 100644
index 00000000000..b14032001a1
Binary files /dev/null and b/docs/images/owenyeo.png differ
diff --git a/docs/images/teozhengyang.png b/docs/images/teozhengyang.png
new file mode 100644
index 00000000000..c9498af882f
Binary files /dev/null and b/docs/images/teozhengyang.png differ
diff --git a/docs/images/ui-breakdown.png b/docs/images/ui-breakdown.png
new file mode 100644
index 00000000000..1f50a016339
Binary files /dev/null and b/docs/images/ui-breakdown.png differ
diff --git a/docs/images/ui-overview.png b/docs/images/ui-overview.png
new file mode 100644
index 00000000000..011fcf60bc0
Binary files /dev/null and b/docs/images/ui-overview.png differ
diff --git a/docs/images/ui-profile-display.png b/docs/images/ui-profile-display.png
new file mode 100644
index 00000000000..9fff32d5659
Binary files /dev/null and b/docs/images/ui-profile-display.png differ
diff --git a/docs/images/ui-reminder-popup.png b/docs/images/ui-reminder-popup.png
new file mode 100644
index 00000000000..91f7f987991
Binary files /dev/null and b/docs/images/ui-reminder-popup.png differ
diff --git a/docs/images/ui-startup.png b/docs/images/ui-startup.png
new file mode 100644
index 00000000000..1dc3e557620
Binary files /dev/null and b/docs/images/ui-startup.png differ
diff --git a/docs/images/ui-view-friend.png b/docs/images/ui-view-friend.png
new file mode 100644
index 00000000000..7a96f73360a
Binary files /dev/null and b/docs/images/ui-view-friend.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..3d31eff3f7e 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,18 +1,29 @@
---
layout: page
-title: AddressBook Level-3
+title: TimetaBRO
---
-[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions)
-[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3)
+[![CI Status](https://github.com/AY2324S1-CS2103T-W12-4/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2324S1-CS2103T-W12-4/tp/actions)
+[![codecov](https://codecov.io/gh/AY2324S1-CS2103-W12-4/tp/branch/master/graph/badge.svg)](https://codecov.io/gh/AY2324S1-CS2103-W12-4/tp)
![Ui](images/Ui.png)
-**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
+**TimetaBRO is a desktop application for arranging meetups with busy friends.**
+Created by NUS students, TimetaBRO is dedicated to fellow NUS students to aid in
+seamless schedule comparison and coordination.
-* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
-* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
+Before TimetaBRO, the typical method of comparing timetables with your peers
+either for casual meetups or group projects consisted of hopping between screenshots of timetables.
+However, with TimetaBRO, you now can store the schedules of your peers on one platform with easy visual comparison,
+as well as our `cft` command that helps you find common free times
+with the people in your friend list almost instantaneously!
+No more wasting time going back and forth with your friends trying to find meetup times,
+and no more jumping around the media of your chats to view your friends timetables!
+Manage your friendships, meetings, and commitments better and faster with TimetaBRO!
+
+* If you are interested in using TimetaBRO, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
+* If you are interested about developing TimetaBRO, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
**Acknowledgements**
diff --git a/docs/team/andrefoo.md b/docs/team/andrefoo.md
new file mode 100644
index 00000000000..fdc7a2c9e5e
--- /dev/null
+++ b/docs/team/andrefoo.md
@@ -0,0 +1,53 @@
+---
+layout: page
+title: Andre Foo's Project Portfolio Page
+---
+## Overview
+TimetaBRO is a scheduling app designed to help users organize their own calendars and those of their friends. It comes
+with functionalities like adding or deleting friends, scheduling or canceling events, setting up reminders, and
+identifying mutual availability among friends. The app works across various operating systems and automatically backs up
+data in a JSON format. For more tech-savvy users, there's an option to edit the data file manually. TimetaBRO features a
+user-friendly command-line interface and includes a summary of commands for quick reference.
+This application is aims to strengthen social ties and streamline event coordination.
+
+## Summary of Contributions
+
+[**Code contributed**](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=andrefoo&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&tabType=authorship&tabAuthor=andrefoo&tabRepo=AY2324S1-CS2103T-W12-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false)
+
+**Enhancements implemented:**
+
+- v1.2 (unimplemented) enhancements: FreeTime attribute added to Person class in Model. Prototyped the common free time and implemented cascading changes in storage, creating JsonAdaptedFreeTime and incorporating it into the rest of Model.
+- Create timetable model for person:
+ - Added `FreeTime` class to encapsulate timeslots for overlapping free times between persons
+ - Added `TimeBlock` abstract class as a superclass for timeslots to be represented in the timetable
+ - Added `Module` class with implemented validation regex to restrict module codes to legitimate formats in NUS
+ - Added `Cca` class to represent Cca timeslots
+ - Added `DatedEvent` class with Date to represent non-recurring events
+ - Added `Schedule` facade class for timetable to encapsulate the schedule of a person
+ - Implement methods to calculate overlapping free times
+ - Implement methods to retrieve, add and sort lists of modules for easier implementation of commands
+
+**Contributions to the User Guide (UG):**
+
+* Add Parameters constraints table
+* Finalised Command Summary table
+* Add Caution blocks and Warnings for beginner users
+* Formatting command blocks
+
+**Contributions to the Developer Guide (DG):**
+
+* Drafted skeleton for the Developer Guide to delegate tasks to the team
+* Describe functionality and component breakdown of `Model`
+* Add all use cases
+* Add design considerations for implementing `timetable` in `Model` component
+
+**Contributions to team-based tasks:**
+
+* Delegate tasks for documentation-related tasks
+* Reviewed, approved and resolved conflicts for team pull requests
+* Bug fixing for commands and documentation flaws
+
+**Contributions beyond the project team:**
+
+* Bug catcher stress test participation
+* Took part in PE-D
diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md
deleted file mode 100644
index 773a07794e2..00000000000
--- a/docs/team/johndoe.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-layout: page
-title: John Doe's Project Portfolio Page
----
-
-### Project: AddressBook Level 3
-
-AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC.
-
-Given below are my contributions to the project.
-
-* **New Feature**: Added the ability to undo/redo previous commands.
- * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command.
- * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them.
- * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands.
- * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}*
-
-* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys.
-
-* **Code contributed**: [RepoSense link]()
-
-* **Project management**:
- * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub
-
-* **Enhancements to existing features**:
- * Updated the GUI color scheme (Pull requests [\#33](), [\#34]())
- * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]())
-
-* **Documentation**:
- * User Guide:
- * Added documentation for the features `delete` and `find` [\#72]()
- * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]()
- * Developer Guide:
- * Added implementation details of the `delete` feature.
-
-* **Community**:
- * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]()
- * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]())
- * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]())
- * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]())
-
-* **Tools**:
- * Integrated a third party library (Natty) to the project ([\#42]())
- * Integrated a new Github plugin (CircleCI) to the team repo
-
-* _{you can add/remove categories in the list above}_
diff --git a/docs/team/kristayeo.md b/docs/team/kristayeo.md
new file mode 100644
index 00000000000..c0414325e3a
--- /dev/null
+++ b/docs/team/kristayeo.md
@@ -0,0 +1,61 @@
+---
+layout: page
+title: Krista Yeo's Project Portfolio Page
+---
+
+# Project Portfolio Page: TimetaBRO
+
+## Overview
+TimetaBRO is an app made for NUS students to better keep track of all their friends' information and schedules.
+It facilitates easy visual comparison between the user and a friend's timetable, and quickly finds common free times with friends.
+
+## Summary of Contributions
+
+**Code contributed:**
+https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=kristayeo&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22
+
+**Enhancements implemented:**
+
+* Added right section of the app that consists of the user profile display and the selected friend profile display.
+* Added a timetable for each display profile
+* Displayed time blocks sorted according to time under their respective day
+* Color coded the time blocks according to their type:
+ * Blue for module (weekly recurring)
+ * Red for CCA (weekly recurring)
+ * Green for dated events (non-recurring)
+* Displayed friend profile upon selection by mouse click from the friend list
+* Refresh display profiles upon relevant command executions (`user`, `edit`, `addschedule`, `addevent`, `rmschedule`, `rmevent`)
+* Created the following UI related classes:
+ * ListCellSelectedEvent.java
+ * SelectedFriendCard.java
+ * UserCard.java
+ * SelectedFriendCard.fxml
+ * UserCard.fxml
+* Redesigned the help pop-up
+
+**Contributions to the User Guide (UG):**
+
+* Wrote the TimetaBRO User Interface
+* Wrote about `addschedule`, `addevent`, `rmschedule`, `rmevent` for both user and friend.
+* Contributed to the documentation of `edit`, `delete`, Viewing friend's profile, `cft`.
+* Ensured all the outputs for their respective features were correct.
+* Organised the UG, ensured that all the features followed the same format.
+* Added necessary additional notes and proofread the UG, cleaned up mistakes.
+
+**Contributions to the Developer Guide (DG):**
+
+* UI component of the Design
+* Implementation: Click to View Friend Timetable Feature
+* Product Scope: Target User Profile and Value Proposition
+* Contributed to the Glossary
+* Did formatting, organising of the code
+
+**Contributions to team-based tasks:**
+
+* Approved and merged pull requests.
+* Wrote README.md and index.md
+
+**Contributions beyond the project team:**
+
+* Took part in Bug CATcher stress test
+* Took part in PE-D
diff --git a/docs/team/lululwtv.md b/docs/team/lululwtv.md
new file mode 100644
index 00000000000..3f737e15817
--- /dev/null
+++ b/docs/team/lululwtv.md
@@ -0,0 +1,52 @@
+---
+layout: page
+title: Edric Khiew's Project Portfolio Page
+---
+
+# Project: TimetaBRO
+
+## Overview
+TimetaBRO is a scheduling application that allows users to manage their own schedule as well as the schedules of their friends. It offers features such as adding and removing friends, adding and removing events, setting reminders, and finding common free times among friends. TimetaBRO is compatible with multiple operating systems and saves data automatically in a JSON file. It also allows advanced users to edit the data file directly. The application offers a command-line interface and provides a command summary for easy reference. TimetaBRO is a powerful tool for enhancing social connections and simplifying event planning.
+
+## Summary of Contributions
+
+**Code contributed:** \
+https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=w12-4&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&tabType=authorship&zFR=false&tabAuthor=lululwtv&tabRepo=AY2324S1-CS2103T-W12-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false
+
+**Enhancements implemented:**
+
+- Changed `add` and `edit` commands to allow users to add friend's free times when adding the friend (past iteration) (feature has been removed)
+- Created `addschedule`, `rmschedule`, `addevent`, `rmevent`, and `cft` commands
+ - For each command class, created a corresponding parser class for the commands
+ - This allows for each command to work better as they have a separate parser class that can help parse the specific inputs for the commands
+- Created some test cases for the commands made by me
+- Fixed seveeral bugs pertaining to `add`, `edit`, `addschedule`, `rmschedule`, `addevent`, `rmevent` commands
+
+**Contributions to the User Guide (UG):**
+
+- Added guides for commands `cft`, `rmevent`, `addevent`, `addschedule`, `rmschedule` commands
+- Fixed UG documentation to align with newer commands
+- Updated UG to include expected features that may not be expected by users, such as:
+ - Informing users in the UG that event names will be changed to all caps even if they did not key it in caps
+ - Add sample code for the commands I have added
+- Wrote FAQ
+- Wrote Known Issues
+- Wrote Command Summary
+
+**Contributions to the Developer Guide (DG):**
+
+- Update DG on how addschedule command works
+ - Created sequence diagram for addschedule command
+- Updated how Logic package works
+- Added implementation section on `addschedule` command
+- Added User Stories
+
+**Contributions to team-based tasks:**
+- Fixed 20 bugs after PE-Dry run, which led to significant improvements in the code
+- Kept track of code quality by fixing all checkstyle issues in gradle
+- Kept track of team's submissions and deadlines, ensuring that all tasks are submitted on time by teammates
+
+**Contributions beyond the project team:**
+
+- Took part in bug-catcher
+- Took part in PE-D
diff --git a/docs/team/owenyeo.md b/docs/team/owenyeo.md
new file mode 100644
index 00000000000..5f64cc3679a
--- /dev/null
+++ b/docs/team/owenyeo.md
@@ -0,0 +1,67 @@
+---
+layout: page
+title: Owen Yeo's Project Portfolio Page
+---
+
+## Project: TimetaBRO
+
+### Overview
+
+TimetaBRO, your ultimate companion for managing your university life, social interactions, and schedules. It allows for easy storage and perusal of your own schedule and your friends! Arrange meetings with ease, and never forget anything!
+
+### Summary of Contributions
+
+**Code contributed:**
+[Link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=owenyeo&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22)
+
+**Enhancements implemented:**
+* **New Feature**: Added the ability to store user data, such as birthdays and schedules. It is stored in a Jackson file `userdata.json`
+ * What it does: Allows the user to store their own data and schedules in TimetaBRO across sessions.
+ * Justification: This feature allows users to quickly access their data and schedules should they need it, and allows for schedule comparisons to occur in other commands. Users also do not need to re-key their information every time they use the app.
+ * Highlights: This enhancement required knowledge about Jackson files, and how to convert models into JSON adapted versions to save on a hard disk. It also required the implementation of a user class. It took me quite long for this.
+* **New Feature**: Added ability to edit user data stored in the app itself.
+ * What it does: Allows user to edit saved data straight from the app's CLI.
+ * Justification: This makes the user experience a lot more smooth as the user does not need to access the `userdata.json` file to update their details.
+ * Highlights: This enhancement required knowledge and tracing of the EditCommand to implement. This was relatively easier as we implemented a command in the codebase tutorial.
+* **JsonAdapted classes**: Added JsonAdapted classes to translate models made by my team to Json friendly formats, and vice versa.
+ * What it does: Allows user to store friends' and their own schedules and data in Json files.
+ * Justification: This allows our new model, the schedule, and its related models to be able to be stored in a data.
+ * Created the follow Json related classes:
+ * JsonAdaptedCca
+ * JsonAdaptedDatedEvent
+ * JsonAdaptedModule
+ * JsonAdaptedSchedule
+ * JsonAdaptedUser
+ * JsonUserDataStorage
+ * JsonSerializableUserData
+* Wrote testcases for storage
+* **Modify person and AddressBook**: Disallowed duplicate phone numbers and emails. Changed notion of equality of same birthday AND same full name.
+ * Justification: It is impossible for 2 different people to have the same phone number or the same email. It is also possible for people to have the same first name. Thus, adding birthday would mitigate this to a higher degree.
+
+
+**Contributions to the User Guide (UG):**
+* Wrote the introduction, giving the user a broad overview of the functionality of TimetaBRO in a friendly manner.
+* Wrote the About section, giving the user a broad overview of the purpose of the UG.
+* Wrote the Quickstart, giving example commands and links to aid the user in the use of the product.
+* Updated tone throughout the UG.
+* Proofread and ensured quality of UG.
+
+**Contributions to the Developer Guide (DG):**
+* Updated the diagrams in Storage and Model
+* Added details about UserDataStorage
+* Added implementation of EditUserCommand
+* Created sequence diagrams for EditUserCommand's execution.
+* Added some extensions in use cases.
+* Proofread and ensured quality of DG.
+
+**Contributions to team-based tasks:**
+* Delegated work to teammates
+* Created issues and assigned them to teammates
+* Kept track of code deadlines
+* Reviewed, approved, and merged pull requests from other tea members. E.g. #93 where I requested change due to code quality transgressions.
+* Created releases for v1.4 and v1.2
+* Ensured labels were correct in PRs I reviewed.
+
+**Contributions beyond the project team:**
+* Took part in Bug CATcher stress test
+* Took part in PE-D
diff --git a/docs/team/teozhengyang.md b/docs/team/teozhengyang.md
new file mode 100644
index 00000000000..26ef312b59c
--- /dev/null
+++ b/docs/team/teozhengyang.md
@@ -0,0 +1,57 @@
+---
+layout: page
+title: Teo Zheng Yang's Project Portfolio Page
+---
+
+## Project: TimetaBRO
+
+### Overview
+
+Timetabro is a desktop app aimed at helping university students at NUS to find friends to have meals with
+and manage their schedules. The user interacts with it using a CLI and it has a GUI created with JavaFX.
+It is written in Java too.
+
+### Summary of Contributions
+
+**Code contributed:**
+
+[RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=teozhengyang&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22)
+
+**Enhancements implemented:**
+
+* **New Feature**: Added the ability to set and remove reminders for dated events in the user's schedule.
+ * What it does: Allows the user to set and remove reminders for dated events in the user's schedule.
+ * Justification: This feature improves the product significantly because it allows the user to be
+ reminded of important events in their schedule.
+ * Highlights: This enhancement required the use of the JavaFX library to create a popup window
+ to remind users of certain events.
+* **New Feature**: Added the ability to add birthdays to the users' contacts and to
+ view the birthdays of the users' contacts.
+ * What it does: Allows the user to add birthdays to the users' contacts and to
+ view the birthdays of the users' contacts.
+ * Justification: This feature improves the product significantly because it allows the user to be
+ reminded of their contacts' birthdays.
+ * Highlights: This enhancement required the use of the JavaFX library to create a popup window
+ * to remind users of their contacts' birthdays.
+* **Test cases**: Added new test cases for new classes and updated existing test cases.
+ * What it does: Test any new classes and existing classes to ensure they are bug-free.
+ * Justification: This ensures that the new features added work as intended and that existing features
+ are not broken.
+
+**Contributions to the User Guide (UG):**
+
+* Section about set and remove reminders for dated events in the user's schedule
+
+**Contributions to the Developer Guide (DG):**
+
+* Section about user stories
+* Section about instructions for manual testing
+* Write about implementation details of the reminders feature
+
+**Contributions to team-based tasks:**
+
+* Reviewed, approved and merged pull requests from other team members
+
+**Contributions beyond the project team:**
+
+* Reported bugs in other teams' products during PE dry run
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 00000000000..b5d4cdd4351
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,10 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ *
+ * The settings file is used to specify which projects to include in your build.
+ *
+ * Detailed information about configuring a multi-project build in Gradle can be found
+ * in the user manual at https://docs.gradle.org/7.4.2/userguide/multi_project_builds.html
+ */
+
+rootProject.name = 'tp'
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 3d6bd06d5af..97e859d8bef 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -19,15 +19,20 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
+import seedu.address.model.user.ReadOnlyUserData;
+import seedu.address.model.user.ReadOnlyUserPrefs;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
import seedu.address.model.util.SampleDataUtil;
import seedu.address.storage.AddressBookStorage;
import seedu.address.storage.JsonAddressBookStorage;
+import seedu.address.storage.JsonUserDataStorage;
import seedu.address.storage.JsonUserPrefsStorage;
import seedu.address.storage.Storage;
import seedu.address.storage.StorageManager;
+import seedu.address.storage.UserDataStorage;
import seedu.address.storage.UserPrefsStorage;
+import seedu.address.ui.Reminder;
import seedu.address.ui.Ui;
import seedu.address.ui.UiManager;
@@ -36,7 +41,7 @@
*/
public class MainApp extends Application {
- public static final Version VERSION = new Version(0, 2, 2, true);
+ public static final Version VERSION = new Version(1, 2, 1, true);
private static final Logger logger = LogsCenter.getLogger(MainApp.class);
@@ -58,7 +63,8 @@ public void init() throws Exception {
UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath());
UserPrefs userPrefs = initPrefs(userPrefsStorage);
AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath());
- storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ UserDataStorage userDataStorage = new JsonUserDataStorage(userPrefs.getUserDataFilePath());
+ storage = new StorageManager(addressBookStorage, userPrefsStorage, userDataStorage);
model = initModelManager(storage, userPrefs);
@@ -90,7 +96,23 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
initialData = new AddressBook();
}
- return new ModelManager(initialData, userPrefs);
+ logger.info("Using data file : " + storage.getUserDataFilePath());
+ Optional userDataOptional;
+ ReadOnlyUserData userData;
+ try {
+ userDataOptional = storage.readUserData();
+ if (!userDataOptional.isPresent()) {
+ logger.info("Creating a new data file " + storage.getUserDataFilePath()
+ + " populated with a sample UserData.");
+ }
+ userData = userDataOptional.orElseGet(SampleDataUtil::getSampleUserData);
+ } catch (DataLoadingException e) {
+ logger.warning("Data file at " + storage.getUserDataFilePath() + " could not be loaded."
+ + " Will be starting with an empty UserData.");
+ userData = new UserData(SampleDataUtil.getSampleUser());
+ }
+
+ return new ModelManager(initialData, userPrefs, userData);
}
private void initLogging(Config config) {
@@ -172,6 +194,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
public void start(Stage primaryStage) {
logger.info("Starting AddressBook " + MainApp.VERSION);
ui.start(primaryStage);
+ Reminder.showReminder(model, primaryStage);
}
@Override
diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java
index 8cf8e15a0f0..6fb7b9e766f 100644
--- a/src/main/java/seedu/address/commons/core/LogsCenter.java
+++ b/src/main/java/seedu/address/commons/core/LogsCenter.java
@@ -65,6 +65,14 @@ public static Logger getLogger(Class clazz) {
requireNonNull(clazz);
return getLogger(clazz.getSimpleName());
}
+ /**
+ * Retrieves the current logging level.
+ *
+ * @return The current logging level as a {@link java.util.logging.Level} object.
+ */
+ public static Level getCurrentLogLevel() {
+ return currentLogLevel;
+ }
/**
* Removes all handlers from the {@code logger}.
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
index 92cd8fa605a..1be46416988 100644
--- a/src/main/java/seedu/address/logic/Logic.java
+++ b/src/main/java/seedu/address/logic/Logic.java
@@ -33,6 +33,8 @@ public interface Logic {
/** Returns an unmodifiable view of the filtered list of persons */
ObservableList getFilteredPersonList();
+ Person getUser();
+
/**
* 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 5aa3b91c7d0..80fe3f4cc71 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/seedu/address/logic/LogicManager.java
@@ -52,6 +52,7 @@ public CommandResult execute(String commandText) throws CommandException, ParseE
try {
storage.saveAddressBook(model.getAddressBook());
+ storage.saveUserData(model.getUserData());
} catch (AccessDeniedException e) {
throw new CommandException(String.format(FILE_OPS_PERMISSION_ERROR_FORMAT, e.getMessage()), e);
} catch (IOException ioe) {
@@ -71,6 +72,11 @@ public ObservableList getFilteredPersonList() {
return model.getFilteredPersonList();
}
+ @Override
+ public Person getUser() {
+ return model.getUser();
+ }
+
@Override
public Path getAddressBookFilePath() {
return model.getAddressBookFilePath();
diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java
index ecd32c31b53..43225444ca2 100644
--- a/src/main/java/seedu/address/logic/Messages.java
+++ b/src/main/java/seedu/address/logic/Messages.java
@@ -43,6 +43,8 @@ public static String format(Person person) {
.append(person.getEmail())
.append("; Address: ")
.append(person.getAddress())
+ .append("; Birthday: ")
+ .append(person.getBirthday())
.append("; Tags: ");
person.getTags().forEach(builder::append);
return builder.toString();
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java
index 5d7185a9680..009096cb2ba 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_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
@@ -26,17 +27,21 @@ public class AddCommand extends Command {
+ PREFIX_PHONE + "PHONE "
+ PREFIX_EMAIL + "EMAIL "
+ PREFIX_ADDRESS + "ADDRESS "
+ + PREFIX_BIRTHDAY + "BIRTHDAY "
+ "[" + PREFIX_TAG + "TAG]...\n"
+ "Example: " + COMMAND_WORD + " "
+ PREFIX_NAME + "John Doe "
+ PREFIX_PHONE + "98765432 "
+ PREFIX_EMAIL + "johnd@example.com "
+ PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "
+ + PREFIX_BIRTHDAY + "2000-01-01 "
+ PREFIX_TAG + "friends "
+ PREFIX_TAG + "owesMoney";
public static final String MESSAGE_SUCCESS = "New person added: %1$s";
public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book";
+ public static final String MESSAGE_DUPLICATE_PHONE = "This phone number already exists in the address book";
+ public static final String MESSAGE_DUPLICATE_EMAIL = "This email already exists in the address book";
private final Person toAdd;
@@ -56,6 +61,14 @@ public CommandResult execute(Model model) throws CommandException {
throw new CommandException(MESSAGE_DUPLICATE_PERSON);
}
+ if (model.hasPhone(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_PHONE);
+ }
+
+ if (model.hasEmail(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_EMAIL);
+ }
+
model.addPerson(toAdd);
return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd)));
}
diff --git a/src/main/java/seedu/address/logic/commands/AddEventCommand.java b/src/main/java/seedu/address/logic/commands/AddEventCommand.java
new file mode 100644
index 00000000000..f63d900d5cd
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddEventCommand.java
@@ -0,0 +1,135 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.timetable.Schedule;
+
+/**
+ * Represents a command to add a non-recurring event to the calendar.
+ * The event can either be a dated event or a meetup event.
+ * A dated event is an event that is added to a friend's schedule or the user's schedule.
+ * A meetup event is an event that is added to the user's schedule and involves meeting up with a friend.
+ * Inherits from the Command class.
+ */
+public class AddEventCommand extends Command {
+
+ public static final String COMMAND_WORD = "addevent";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Adds a non-recurring event to the calendar.\n"
+ + "Parameters: "
+ + "INDEX "
+ + "en/EVENT_NAME "
+ + "h/[Date [YYYY-MM-DD] START_TIME [HHMM] END_TIME [HHMM]] "
+ + "r/[REMINDER: y/n] \n"
+ + "Example: " + COMMAND_WORD + " "
+ + "1 "
+ + "en/CS2103T Final Exam "
+ + "h/2020-03-02 1400 1600 "
+ + "r/y \n"
+ + "Note: "
+ + "Index should be the index of "
+ + "the friend you are adding the dated event to or 'user' "
+ + "if you would like to add the event to yourself \n";
+
+ public static final String MESSAGE_SUCCESS = "New event added: ";
+
+ private final String eventName;
+ private final Index index;
+ private final String schedule;
+ private final String reminder;
+
+ /**
+ * Constructs an AddEventCommand object with the specified event name, index, schedule,
+ * reminder and event type.
+ * @param eventName The name of the event.
+ * @param index The index of the friend to add the event to.
+ * @param schedule The schedule of the event.
+ * @param reminder The reminder of the event.
+ */
+ public AddEventCommand(String eventName, Index index,
+ String schedule, String reminder) {
+
+ requireAllNonNull(schedule);
+
+ this.eventName = eventName;
+ this.schedule = schedule;
+ this.index = index;
+ this.reminder = reminder;
+ }
+
+ /**
+ * Constructs an AddEventCommand object with the specified event name, schedule,
+ * reminder and event type.
+ * @param eventName The name of the event.
+ * @param schedule The schedule of the event.
+ * @param reminder The reminder of the event.
+ */
+ public AddEventCommand(String eventName, String schedule,
+ String reminder) {
+
+ this(eventName, null, schedule, reminder);
+ }
+
+ /**
+ * When successfully executed, it should add a new event to the user's timetable.
+ * @param model {@code Model} which the command should operate on.
+ * @return A command result in the form of a string.
+ * @throws CommandException If the command is invalid.
+ */
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ Person friend;
+
+ try {
+ if (this.index == null) {
+ friend = model.getUser();
+ } else {
+ List lastShownList = model.getFilteredPersonList();
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX + "\n"
+ + "Index can be max " + lastShownList.size() + "!");
+ }
+ friend = model.getFilteredPersonList().get(index.getZeroBased());
+ }
+
+ Schedule friendSchedule = friend.getSchedule();
+ friendSchedule.addDatedEvent(eventName + " " + schedule + " " + reminder);
+ return new CommandResult(MESSAGE_SUCCESS + "\nDated Event:\n" + eventName + " "
+ + schedule + " to " + friend.getName(), false, false, true, false);
+ } catch (Exception e) {
+ throw new CommandException(e.getMessage());
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof AddEventCommand)) {
+ return false;
+ }
+
+ AddEventCommand other = (AddEventCommand) o;
+ if (index == null && other.index != null) {
+ return false;
+ } else if (index != null && other.index == null) {
+ return false;
+ } else {
+ return eventName.equals(other.eventName)
+ && schedule.equals(other.schedule)
+ && reminder.equals(other.reminder);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/AddScheduleCommand.java b/src/main/java/seedu/address/logic/commands/AddScheduleCommand.java
new file mode 100644
index 00000000000..8eb01ec5ae9
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddScheduleCommand.java
@@ -0,0 +1,151 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.timetable.Schedule;
+
+/**
+ * Represents a command to add a schedule to a specified contact.
+ * Inherits from the Command class.
+ */
+public class AddScheduleCommand extends Command {
+
+ public static final String COMMAND_WORD = "addschedule";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a schedule to the specified contact.\n"
+ + "Parameters: "
+ + "INDEX "
+ + "type/EVENT_TYPE "
+ + "en/EVENT_NAME "
+ + "h/[DAY_OF_WEEK START_TIME [HHMM] END_TIME [HHMM]]\n"
+ + "Example: " + COMMAND_WORD
+ + " 1"
+ + " type/cca"
+ + " en/Basketball"
+ + " h/Monday 1400 1600\n"
+ + "NOTE: If you want to add a cca/module to yourself, use addschedule user\n"
+ + "Example: " + COMMAND_WORD
+ + " user"
+ + " type/cca"
+ + " en/Basketball"
+ + " h/Monday 1400 1600";
+
+ public static final String MESSAGE_SUCCESS = "New event added: ";
+
+ private final String eventName;
+ private final String eventType;
+ private final Index index;
+ private final String schedule;
+
+ /**
+ * Constructs an AddScheduleCommand object with the specified event name, index, schedule and event type.
+ * @param eventName The name of the event.
+ * @param eventType The type of the event.
+ * @param index The index of the friend to add the event to.
+ * @param schedule The schedule of the event.
+ */
+ public AddScheduleCommand(String eventName, String eventType, Index index, String schedule) {
+ this.eventName = eventName;
+ this.eventType = eventType;
+ this.index = index;
+ this.schedule = schedule;
+ }
+
+ /**
+ * Constructs an AddScheduleCommand object with the specified event name, schedule and event type.
+ * @param eventName The name of the event.
+ * @param eventType The type of the event.
+ * @param schedule The schedule of the event.
+ */
+ public AddScheduleCommand(String eventName, String eventType, String schedule) {
+ this(eventName, eventType, null, schedule);
+ }
+
+ /**
+ * Executes the AddScheduleCommand and adds the specified event to the contact's calendar.
+ * If the index is not null, the event will be added to the friend's calendar.
+ * Otherwise, the event will be added to the user's calendar.
+ *
+ * @param model {@code Model} which the command should operate on.
+ * @return Feedback message of the operation result, along with other information.
+ * @throws CommandException If the event to be added already exists or the event type is invalid.
+ */
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ Person friend;
+
+ try {
+ if (this.index == null) {
+ friend = model.getUser();
+ } else {
+ List lastShownList = model.getFilteredPersonList();
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX + "\n"
+ + "Index can be max " + lastShownList.size() + "!");
+ }
+ friend = model.getFilteredPersonList().get(index.getZeroBased());
+ }
+
+ Schedule friendSchedule = friend.getSchedule();
+
+ switch (eventType) {
+ // If the event type is cca, add the event to the friend's cca schedule
+ case "cca":
+ friendSchedule.addCca(eventName + " " + schedule);
+ return new CommandResult(MESSAGE_SUCCESS
+ + "\nCCA:\n"
+ + eventName
+ + " "
+ + schedule
+ + " to "
+ + friend.getName(), false, false, true, false);
+ case "module":
+ // If the event type is module, add the event to the friend's module schedule
+ friendSchedule = friend.getSchedule();
+ friendSchedule.addModule(eventName + " " + schedule);
+ return new CommandResult(MESSAGE_SUCCESS
+ + "\nModule:\n"
+ + eventName
+ + " "
+ + schedule
+ + " to "
+ + friend.getName(), false, false, true, false);
+ // If the event type is invalid, throw an exception
+ default:
+ throw new CommandException("Invalid event type!"
+ + "\n Event type can only be 'Module' or 'CCA'");
+ }
+ } catch (Exception e) {
+ throw new CommandException(e.getMessage());
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof AddScheduleCommand)) {
+ return false;
+ }
+
+ AddScheduleCommand other = (AddScheduleCommand) o;
+ if (index == null && other.index != null) {
+ return false;
+ } else if (index != null && other.index == null) {
+ return false;
+ } else {
+ return eventName.equals(other.eventName)
+ && eventType.equals(other.eventType)
+ && schedule.equals(other.schedule);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java
index 249b6072d0d..f22d609e28f 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/seedu/address/logic/commands/CommandResult.java
@@ -19,13 +19,25 @@ public class CommandResult {
/** The application should exit. */
private final boolean exit;
+ /**
+ * The application should check if it is editable.
+ */
+ private final boolean refresh;
+
+ /**
+ * The application should check if it is a commonfreetime.
+ */
+ private final boolean commonFreetime;
+
/**
* Constructs a {@code CommandResult} with the specified fields.
*/
- public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
+ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit, boolean refresh, boolean cft) {
this.feedbackToUser = requireNonNull(feedbackToUser);
this.showHelp = showHelp;
this.exit = exit;
+ this.refresh = refresh;
+ this.commonFreetime = cft;
}
/**
@@ -33,7 +45,11 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
* and other fields set to their default value.
*/
public CommandResult(String feedbackToUser) {
- this(feedbackToUser, false, false);
+ this(feedbackToUser, false, false, false, false);
+ }
+
+ public CommandResult(String feedbacktoUser, boolean cft) {
+ this(feedbacktoUser, false, false, false, cft);
}
public String getFeedbackToUser() {
@@ -48,6 +64,14 @@ public boolean isExit() {
return exit;
}
+ public boolean isRefresh() {
+ return refresh;
+ }
+
+ public boolean isCommonFreetime() {
+ return commonFreetime;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
diff --git a/src/main/java/seedu/address/logic/commands/CommonFreetimeCommand.java b/src/main/java/seedu/address/logic/commands/CommonFreetimeCommand.java
new file mode 100644
index 00000000000..3d4075e8ede
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/CommonFreetimeCommand.java
@@ -0,0 +1,184 @@
+package seedu.address.logic.commands;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.timetable.FreeTime;
+import seedu.address.model.person.timetable.Schedule;
+
+/**
+ * Represents a command to find all contacts with the same free time as the User.
+ * If no name is specified, the command returns the user's common free time with all contacts.
+ * If a name is specified, the command returns the user's common free time with the specified contact.
+ */
+public class CommonFreetimeCommand extends Command {
+
+ public static final String COMMAND_WORD = "cft";
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Finds all contacts with the same free time as the User.\n"
+ + "Example: " + COMMAND_WORD;
+ public static final String MESSAGE_COMMON_FREETIME_SUCCESS =
+ "Here are the contacts with the same free time as you: \n";
+ public static final String MESSAGE_NO_FREE_TIME = "You have no free time!";
+ public static final String MESSAGE_NO_CONTACTS = "You have no contacts with the same free time as you!";
+
+ private Index index = null; // name of person (user) to find common free time with
+
+ public CommonFreetimeCommand() {
+ }
+
+ public CommonFreetimeCommand(Index index) {
+ this.index = index;
+ }
+
+ /**
+ * Executes the CommonFreetimeCommand to find common free time between user and person.
+ * If no name is specified, the command returns the user's common free time with all contacts.
+ * If a name is specified, the command returns the user's common free time with the specified contact.
+ *
+ * @param model {@code Model} which the command should operate on.
+ * @return a CommandResult object that contains the result of executing the command.
+ * @throws CommandException if an error occurs during command execution.
+ */
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ ArrayList overlappingContacts = new ArrayList<>();
+ Schedule userSchedule = model.getUser().getSchedule();
+ Person user = model.getUser();
+
+ // If user has no free time, return error message
+ if (!model.getUser().getSchedule().hasFreeTime()) {
+ throw new CommandException(MESSAGE_NO_FREE_TIME);
+ }
+ // If no name is specified, return the user's common free time with all contacts
+ if (this.index == null) {
+ ObservableList contacts = model.getAddressBook().getPersonList();
+ ArrayList commonFreeTime = new ArrayList<>();
+
+ getContactsWithFreeTime(contacts, commonFreeTime, overlappingContacts, userSchedule);
+
+ if (commonFreeTime.isEmpty()) {
+ throw new CommandException(MESSAGE_NO_CONTACTS);
+ } else {
+ return new CommandResult(createCommonFreeTimeMessage(overlappingContacts, user).toString());
+ }
+ } else {
+ List lastShownList = model.getFilteredPersonList();
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+ try {
+ Person friend = lastShownList.get(index.getZeroBased());
+
+ return new CommandResult(createCommonFreeTimeMessage(user, friend).toString());
+ } catch (Exception e) {
+ throw new CommandException("You do not have common free times with this contact!");
+ }
+ }
+ }
+
+ /**
+ * Returns a message indicating that the user and the given friend have no common free time.
+ *
+ * @param friend the friend to check for common free time
+ * @return a message indicating that the user and the given friend have no common free time
+ */
+ public static String createNoOverlapFriendMessage(Person friend) {
+ return "You and " + friend.getName().toString() + " have no common free time!\n";
+ }
+
+ /**
+ * Retrieves the contacts that have free time in common with the user's schedule,
+ * and adds their overlapping free times to the commonFreeTime list.
+ * @param contacts The list of contacts to check for overlapping free times.
+ * @param commonFreeTime The list to add the overlapping free times to.
+ * @param overlappingContacts The list to add the contacts with overlapping free times to.
+ * @param userSchedule The user's schedule to compare with the contacts' schedules.
+ */
+ public void getContactsWithFreeTime(ObservableList contacts,
+ ArrayList commonFreeTime, ArrayList overlappingContacts, Schedule userSchedule) {
+ for (Person contact : contacts) {
+ Schedule contactSchedule = contact.getSchedule();
+ if (userSchedule.getThisWeeksFreeTimesWith(contactSchedule).equals(null)) {
+ continue;
+ } else {
+ overlappingContacts.add(contact);
+ commonFreeTime.addAll(userSchedule.getThisWeeksFreeTimesWith(contactSchedule));
+ }
+ }
+ }
+
+ /**
+ * Creates a message containing the common free times between two persons.
+ *
+ * @param user the first person
+ * @param friend the second person
+ * @return a StringBuilder containing the common free times between the two persons
+ * @throws CommandException if there are no common free times between the two persons
+ */
+ public StringBuilder createCommonFreeTimeMessage(Person user, Person friend) throws CommandException {
+ Schedule userSchedule = user.getSchedule();
+ Schedule friendSchedule = friend.getSchedule();
+ List commonFreeTimeWithFriend = userSchedule.getThisWeeksFreeTimesWith(friendSchedule);
+ if (commonFreeTimeWithFriend.isEmpty()) {
+ return new StringBuilder(createNoOverlapFriendMessage(friend));
+ } else {
+ StringBuilder sb = new StringBuilder("You have common free times with "
+ + friend.getName().toString()
+ + " at:\n");
+ for (FreeTime cft : commonFreeTimeWithFriend) {
+ sb.append(cft.toString()).append("\n");
+ }
+ return sb;
+ }
+ }
+
+ /**
+ * Creates a message containing the common free time slots for a list of overlapping contacts.
+ * @param user The user to check for common free time slots.
+ * @param overlappingContacts An ArrayList of Person objects representing the overlapping contacts.
+ * @return A StringBuilder object containing the message with the common free time slots
+ * for the overlapping contacts.
+ */
+ public StringBuilder createCommonFreeTimeMessage(ArrayList overlappingContacts, Person user) {
+ try {
+ StringBuilder sb = new StringBuilder(MESSAGE_COMMON_FREETIME_SUCCESS);
+ for (Person contactName : overlappingContacts) {
+ sb.append(createCommonFreeTimeMessage(user, contactName).toString());
+ }
+ return sb;
+ } catch (CommandException e) {
+ return new StringBuilder(e.getMessage());
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof CommonFreetimeCommand)) {
+ return false;
+ }
+
+ CommonFreetimeCommand otherCommonFreetimeCommand = (CommonFreetimeCommand) other;
+ return (index == null && otherCommonFreetimeCommand.index == null)
+ || index.equals(otherCommonFreetimeCommand.index);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("index", index)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java
index 3dd85a8ba90..7874f9ccfc1 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, false, true, false, false);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java
index bf824f91bd0..492a3633fbd 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, true, false, false, false);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/RemoveEventCommand.java b/src/main/java/seedu/address/logic/commands/RemoveEventCommand.java
new file mode 100644
index 00000000000..680d0839d3e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/RemoveEventCommand.java
@@ -0,0 +1,118 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.timetable.DatedEvent;
+import seedu.address.model.person.timetable.Schedule;
+
+
+/**
+ * Represents a command to remove an event from a contact's calendar.
+ * The event can either be a dated event or a meetup event.
+ * If the index is "user", the event will be removed from the user's calendar.
+ * Otherwise, the event will be removed from the specified contact's calendar.
+ */
+public class RemoveEventCommand extends Command {
+
+ public static final String COMMAND_WORD = "rmevent";
+ public static final String MESSAGE_USAGE =
+ "rmevent: Removes an event from the specified contact's calendar.\n"
+ + "Parameters: "
+ + "INDEX "
+ + "en/EVENT_NAME \n"
+ + "Example: " + COMMAND_WORD
+ + " 1"
+ + " en/CS2103T Final Exam\n"
+ + "NOTE: If you want to remove an event from your calendar, use rmevent user.\n"
+ + "Example: " + COMMAND_WORD
+ + " user"
+ + " en/CS2103T Final Exam";
+
+ private final String eventName;
+ private final Index index;
+
+ /**
+ * Represents a command that removes an event from the address book.
+ * The event to be removed is specified by its name, type and index in the list.
+ */
+ public RemoveEventCommand(String eventName, Index index) {
+ this.eventName = eventName;
+ this.index = index;
+ }
+
+ /**
+ * Removes an event from the user's schedule or a friend's schedule.
+ * The type of event to be removed can either be a dated event or a meetup event.
+ * If the index is not null, the event will be removed from the friend's schedule.
+ * Otherwise, the event will be removed from the user's schedule.
+ *
+ * @param model {@code Model} which the command should operate on.
+ * @return Feedback message of the operation result, along with other information.
+ * @throws CommandException If the event to be removed does not exist or the event type is invalid.
+ */
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ Person friend;
+
+ Schedule userSchedule = model.getUser().getSchedule();
+ if (index == null) {
+ userSchedule.deleteDatedEvent(eventName);
+ return new CommandResult("Dated Event '" + eventName
+ + "' deleted from your calendar!", false, false, true, false);
+ } else {
+ List lastShownList = model.getFilteredPersonList();
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX + "\n"
+ + "Index can be max " + lastShownList.size() + "!");
+ }
+ friend = model.getFilteredPersonList().get(index.getZeroBased());
+ friend.getSchedule().deleteDatedEvent(eventName);
+ return new CommandResult("Dated Event '" + eventName + "' deleted from "
+ + friend.getName().toString() + "'s calendar!", false, false, true, false);
+ }
+ }
+
+ /**
+ * Returns a string representation of the given list of DatedEvents.
+ * Each event's string representation is separated by a newline character.
+ *
+ * @param events The list of DatedEvents to be converted to a string.
+ * @return A string representation of the given list of DatedEvents.
+ */
+ public String listEvents(List events) {
+ String result = "";
+ for (DatedEvent event : events) {
+ result += event.toString() + "\n";
+ }
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof RemoveEventCommand)) {
+ return false;
+ }
+
+ RemoveEventCommand other = (RemoveEventCommand) o;
+ if (index == null && other.index != null) {
+ return false;
+ } else if (index != null && other.index == null) {
+ return false;
+ } else {
+ return eventName.equals(other.eventName);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/commands/RemoveReminderCommand.java b/src/main/java/seedu/address/logic/commands/RemoveReminderCommand.java
new file mode 100644
index 00000000000..54209b2e931
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/RemoveReminderCommand.java
@@ -0,0 +1,73 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Optional;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.timetable.DatedEvent;
+
+/**
+ * Remove reminders of events in user's timetable that will be sent to user.
+ * Keyword matching is case insensitive.
+ */
+public class RemoveReminderCommand extends Command {
+ public static final String COMMAND_WORD = "rmReminder";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Remove reminders of events in user's timetable \n"
+ + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
+ + "Example: " + COMMAND_WORD + " cs2103t lesson";
+
+ public static final String MESSAGE_NO_EVENT = "No such event exists!";
+
+ public static final String MESSAGE_REMOVE_REMINDER_SUCCESS = "Reminder removed for following event: \n";
+
+ private final String eventName;
+
+ public RemoveReminderCommand(String eventName) {
+ this.eventName = eventName;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ Optional event = model.getUser().getDatedEvent(eventName);
+ if (event.isEmpty()) {
+ throw new CommandException(MESSAGE_NO_EVENT);
+ } else {
+ model.getUser().removeReminder(event.get());
+ StringBuilder sb = new StringBuilder(MESSAGE_REMOVE_REMINDER_SUCCESS);
+ sb.append(model.getUser().getDatedEvent(eventName).get().getName());
+ return new CommandResult(sb.toString());
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof RemoveReminderCommand)) {
+ return false;
+ }
+
+ RemoveReminderCommand otherRemoveCommand = (RemoveReminderCommand) other;
+ return eventName.equals(otherRemoveCommand.eventName);
+ }
+
+ /**
+ * Returns a string representation of the RemoveReminderCommand object.
+ * Includes the event name for which the reminder is to be removed.
+ *
+ * @return String representation of the RemoveReminderCommand object.
+ */
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("Remove Reminder for", eventName)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/RemoveScheduleCommand.java b/src/main/java/seedu/address/logic/commands/RemoveScheduleCommand.java
new file mode 100644
index 00000000000..685acf21ab2
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/RemoveScheduleCommand.java
@@ -0,0 +1,153 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.timetable.DatedEvent;
+import seedu.address.model.person.timetable.Schedule;
+
+/**
+ * Represents a command to remove an event from a contact's calendar.
+ * The event can be either a dated event or a meetup event.
+ * If the index is "user", the event will be removed from the user's calendar.
+ * Otherwise, the event will be removed from the specified contact's calendar.
+ */
+public class RemoveScheduleCommand extends Command {
+
+ public static final String COMMAND_WORD = "rmschedule";
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Removes an event from the specified contact's calendar.\n"
+ + "Parameters: "
+ + "INDEX "
+ + "type/EVENT_TYPE "
+ + "en/EVENT_NAME\n"
+ + "Example: " + COMMAND_WORD
+ + " 1"
+ + " type/cca"
+ + " en/Basketball\n"
+ + "NOTE: If you want to remove an event from yourself, use index user\n"
+ + "Example: " + COMMAND_WORD
+ + " user"
+ + " type/cca"
+ + " en/Basketball";
+
+ private final String eventName;
+ private final String eventType;
+ private final Index index;
+
+ /**
+ * Constructs a RemoveScheduleCommand object with the specified event name, event type and index.
+ * @param eventName The name of the event to be removed.
+ * @param eventType The type of the event to be removed.
+ * @param index The index of the contact whose calendar the event is to be removed from.
+ */
+ public RemoveScheduleCommand(String eventName, String eventType, Index index) {
+ this.eventName = eventName;
+ this.eventType = eventType;
+ this.index = index;
+ }
+
+ /**
+ * Constructs a RemoveScheduleCommand object with the specified event name and event type.
+ * @param eventName The name of the event to be removed.
+ * @param eventType The type of the event to be removed.
+ */
+ public RemoveScheduleCommand(String eventName, String eventType) {
+ this(eventName, eventType, null);
+ }
+
+ /**
+ * Executes the RemoveScheduleCommand and removes the specified event from the contact's calendar.
+ * @param model The model to execute the command on.
+ * @return The CommandResult of the execution.
+ * @throws CommandException If an error occurs during execution.
+ */
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ Person friend;
+
+ Schedule userSchedule = model.getUser().getSchedule();
+ if (this.index == null) {
+ friend = model.getUser();
+ } else {
+ List lastShownList = model.getFilteredPersonList();
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX + "\n"
+ + "Index can be max " + lastShownList.size() + "!");
+ }
+ friend = model.getFilteredPersonList().get(index.getZeroBased());
+ }
+
+ switch (eventType) {
+ case "cca":
+ if (index == null) {
+ userSchedule.deleteCca(eventName);
+ return new CommandResult("CCA '" + eventName
+ + "' deleted from your calendar!", false, false, true, false);
+ } else {
+ friend = model.getFilteredPersonList().get(index.getZeroBased());
+ friend.getSchedule().deleteCca(eventName);
+ return new CommandResult("CCA '" + eventName + "' deleted from "
+ + friend.getName().toString()
+ + "'s calendar!", false, false, true, false);
+ }
+ case "module":
+ if (index == null) {
+ userSchedule.deleteModule(eventName);
+ return new CommandResult("Module '" + eventName
+ + "' deleted from your calendar!", false, false, true, false);
+ } else {
+ friend = model.getFilteredPersonList().get(index.getZeroBased());
+ friend.getSchedule().deleteModule(eventName);
+ return new CommandResult("Module '" + eventName + "' deleted from "
+ + friend.getName().toString()
+ + "'s calendar!", false, false, true, false);
+ }
+ default:
+ throw new CommandException("Invalid event type!\n"
+ + "Event type must either be 'cca' or 'module'!\n");
+ }
+ }
+
+ /**
+ * Returns a string representation of the specified list of events.
+ * @param events The list of events to be represented as a string.
+ * @return The string representation of the list of events.
+ */
+ public String listEvents(List events) {
+ String result = "";
+ for (DatedEvent event : events) {
+ result += event.toString() + "\n";
+ }
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof RemoveScheduleCommand)) {
+ return false;
+ }
+
+ RemoveScheduleCommand other = (RemoveScheduleCommand) o;
+ if (index == null && other.index != null) {
+ return false;
+ } else if (index != null && other.index == null) {
+ return false;
+ } else {
+ return eventName.equals(other.eventName)
+ && eventType.equals(other.eventType);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/commands/SetReminderCommand.java b/src/main/java/seedu/address/logic/commands/SetReminderCommand.java
new file mode 100644
index 00000000000..8910774e763
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/SetReminderCommand.java
@@ -0,0 +1,70 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Optional;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.timetable.DatedEvent;
+
+/**
+ * Set reminders of events in user's timetable that will be sent to user.
+ * Keyword matching is case insensitive.
+ */
+public class SetReminderCommand extends Command {
+
+ public static final String COMMAND_WORD = "setReminder";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Set reminders of events in user's timetable"
+ + " that will be sent to user.\n"
+ + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
+ + "Example: " + COMMAND_WORD + " cs2103 lesson";
+
+ public static final String MESSAGE_NO_EVENT = "No such event exists!";
+
+ public static final String MESSAGE_SET_REMINDER_SUCCESS = "Reminder set for following event: \n";
+
+ private final String eventName;
+
+ public SetReminderCommand(String eventName) {
+ this.eventName = eventName;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ Optional event = model.getUser().getDatedEvent(eventName);
+ if (event.isEmpty()) {
+ throw new CommandException(MESSAGE_NO_EVENT);
+ } else {
+ model.getUser().setReminder(event.get());
+ StringBuilder sb = new StringBuilder(MESSAGE_SET_REMINDER_SUCCESS);
+ sb.append(model.getUser().getDatedEvent(eventName).get().getName());
+ return new CommandResult(sb.toString());
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof SetReminderCommand)) {
+ return false;
+ }
+
+ SetReminderCommand otherSetCommand = (SetReminderCommand) other;
+ return eventName.equals(otherSetCommand.eventName);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("Set Reminder for", eventName)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/edit/EditCommand.java
similarity index 56%
rename from src/main/java/seedu/address/logic/commands/EditCommand.java
rename to src/main/java/seedu/address/logic/commands/edit/EditCommand.java
index 4b581c7331e..5e663935748 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/edit/EditCommand.java
@@ -1,31 +1,31 @@
-package seedu.address.logic.commands;
+package seedu.address.logic.commands.edit;
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_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
-import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
import java.util.Set;
import seedu.address.commons.core.index.Index;
-import seedu.address.commons.util.CollectionUtil;
import seedu.address.commons.util.ToStringBuilder;
import seedu.address.logic.Messages;
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Birthday;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.timetable.Schedule;
import seedu.address.model.tag.Tag;
/**
@@ -43,14 +43,17 @@ public class EditCommand extends Command {
+ "[" + PREFIX_PHONE + "PHONE] "
+ "[" + PREFIX_EMAIL + "EMAIL] "
+ "[" + PREFIX_ADDRESS + "ADDRESS] "
+ + "[" + PREFIX_BIRTHDAY + "BIRTHDAY] "
+ "[" + PREFIX_TAG + "TAG]...\n"
+ "Example: " + COMMAND_WORD + " 1 "
+ PREFIX_PHONE + "91234567 "
+ PREFIX_EMAIL + "johndoe@example.com";
- public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s";
+ public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person %1$s";
public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book.";
+ public static final String MESSAGE_DUPLICATE_PHONE = "This phone number already exists in the address book.";
+ public static final String MESSAGE_DUPLICATE_EMAIL = "This email already exists in the address book.";
private final Index index;
private final EditPersonDescriptor editPersonDescriptor;
@@ -83,9 +86,18 @@ public CommandResult execute(Model model) throws CommandException {
throw new CommandException(MESSAGE_DUPLICATE_PERSON);
}
+ if (!personToEdit.isSamePhone(editedPerson) && model.hasPhone(editedPerson)) {
+ throw new CommandException(MESSAGE_DUPLICATE_PHONE);
+ }
+
+ if (!personToEdit.isSameEmail(editedPerson) && model.hasEmail(editedPerson)) {
+ throw new CommandException(MESSAGE_DUPLICATE_EMAIL);
+ }
+
model.setPerson(personToEdit, editedPerson);
model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson)));
+ return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson)),
+ false, false, true, false);
}
/**
@@ -99,9 +111,11 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript
Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone());
Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail());
Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress());
+ Birthday updatedBirthday = editPersonDescriptor.getBirthday().orElse(personToEdit.getBirthday());
+ Schedule updatedSchedule = personToEdit.getSchedule();
Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
-
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
+ return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedBirthday,
+ updatedSchedule, updatedTags);
}
@Override
@@ -128,115 +142,4 @@ public String toString() {
.toString();
}
- /**
- * Stores the details to edit the person with. Each non-empty field value will replace the
- * corresponding field value of the person.
- */
- public static class EditPersonDescriptor {
- private Name name;
- private Phone phone;
- private Email email;
- private Address address;
- private Set tags;
-
- public EditPersonDescriptor() {}
-
- /**
- * Copy constructor.
- * A defensive copy of {@code tags} is used internally.
- */
- public EditPersonDescriptor(EditPersonDescriptor toCopy) {
- setName(toCopy.name);
- setPhone(toCopy.phone);
- setEmail(toCopy.email);
- setAddress(toCopy.address);
- setTags(toCopy.tags);
- }
-
- /**
- * Returns true if at least one field is edited.
- */
- public boolean isAnyFieldEdited() {
- return CollectionUtil.isAnyNonNull(name, phone, email, address, tags);
- }
-
- public void setName(Name name) {
- this.name = name;
- }
-
- public Optional getName() {
- return Optional.ofNullable(name);
- }
-
- public void setPhone(Phone phone) {
- this.phone = phone;
- }
-
- public Optional getPhone() {
- return Optional.ofNullable(phone);
- }
-
- public void setEmail(Email email) {
- this.email = email;
- }
-
- public Optional getEmail() {
- return Optional.ofNullable(email);
- }
-
- public void setAddress(Address address) {
- this.address = address;
- }
-
- public Optional getAddress() {
- return Optional.ofNullable(address);
- }
-
- /**
- * Sets {@code tags} to this object's {@code tags}.
- * A defensive copy of {@code tags} is used internally.
- */
- public void setTags(Set tags) {
- this.tags = (tags != null) ? new HashSet<>(tags) : null;
- }
-
- /**
- * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException}
- * if modification is attempted.
- * Returns {@code Optional#empty()} if {@code tags} is null.
- */
- public Optional> getTags() {
- return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof EditPersonDescriptor)) {
- return false;
- }
-
- EditPersonDescriptor otherEditPersonDescriptor = (EditPersonDescriptor) other;
- return Objects.equals(name, otherEditPersonDescriptor.name)
- && Objects.equals(phone, otherEditPersonDescriptor.phone)
- && Objects.equals(email, otherEditPersonDescriptor.email)
- && Objects.equals(address, otherEditPersonDescriptor.address)
- && Objects.equals(tags, otherEditPersonDescriptor.tags);
- }
-
- @Override
- public String toString() {
- return new ToStringBuilder(this)
- .add("name", name)
- .add("phone", phone)
- .add("email", email)
- .add("address", address)
- .add("tags", tags)
- .toString();
- }
- }
}
diff --git a/src/main/java/seedu/address/logic/commands/edit/EditPersonDescriptor.java b/src/main/java/seedu/address/logic/commands/edit/EditPersonDescriptor.java
new file mode 100644
index 00000000000..77e70b2f596
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/edit/EditPersonDescriptor.java
@@ -0,0 +1,175 @@
+package seedu.address.logic.commands.edit;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.address.commons.util.CollectionUtil;
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.Birthday;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Phone;
+import seedu.address.model.person.timetable.Schedule;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Stores the details to edit the person with. Each non-empty field value will replace the
+ * corresponding field value of the person.
+ */
+public class EditPersonDescriptor {
+ private Name name;
+ private Phone phone;
+ private Email email;
+ private Address address;
+ private Birthday birthday;
+ private Schedule schedule;
+ private Set tags;
+
+ public EditPersonDescriptor() {
+ schedule = new Schedule();
+ }
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
+ setName(toCopy.name);
+ setPhone(toCopy.phone);
+ setEmail(toCopy.email);
+ setAddress(toCopy.address);
+ setBirthday(toCopy.birthday);
+ setSchedule(toCopy.schedule);
+ setTags(toCopy.tags);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(name, phone, email, address, birthday, tags);
+ }
+
+ public void setName(Name name) {
+ this.name = name;
+ }
+
+ public Optional getName() {
+ return Optional.ofNullable(name);
+ }
+
+ public void setPhone(Phone phone) {
+ this.phone = phone;
+ }
+
+ public Optional getPhone() {
+ return Optional.ofNullable(phone);
+ }
+
+ public void setEmail(Email email) {
+ this.email = email;
+ }
+
+ public Optional getEmail() {
+ return Optional.ofNullable(email);
+ }
+
+ public void setAddress(Address address) {
+ this.address = address;
+ }
+
+ public Optional getAddress() {
+ return Optional.ofNullable(address);
+ }
+
+ /**
+ * Sets {@code birthday} to this object's {@code birthday}.
+ * A defensive copy of {@code birthday} is used internally.
+ */
+ public void setBirthday(Birthday birthday) {
+ this.birthday = birthday;
+ }
+
+ /**
+ * Returns an unmodifiable birthday, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code birthday} is null.
+ */
+ public Optional getBirthday() {
+ return Optional.ofNullable(birthday);
+ }
+
+ /**
+ * Sets {@code tags} to this object's {@code tags}.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public void setTags(Set tags) {
+ this.tags = (tags != null) ? new HashSet<>(tags) : null;
+ }
+
+ /**
+ * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code tags} is null.
+ */
+ public Optional> getTags() {
+ return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
+ }
+
+ /**
+ * Sets {@code freeTimes} to this object's {@code freeTimes}.
+ * A defensive copy of {@code freeTimes} is used internally.
+ */
+ public void setSchedule(Schedule schedule) {
+ this.schedule = schedule;
+ }
+
+ /**
+ * Returns an unmodifiable freeTimes set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code freeTimes} is null.
+ */
+ public Optional getSchedule() {
+ return (schedule != null) ? Optional.of(schedule) : Optional.empty();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditPersonDescriptor)) {
+ return false;
+ }
+
+ EditPersonDescriptor otherEditPersonDescriptor = (EditPersonDescriptor) other;
+ return Objects.equals(name, otherEditPersonDescriptor.name)
+ && Objects.equals(phone, otherEditPersonDescriptor.phone)
+ && Objects.equals(email, otherEditPersonDescriptor.email)
+ && Objects.equals(address, otherEditPersonDescriptor.address)
+ && Objects.equals(birthday, otherEditPersonDescriptor.birthday)
+ && Objects.equals(schedule, otherEditPersonDescriptor.schedule)
+ && Objects.equals(tags, otherEditPersonDescriptor.tags);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("name", name)
+ .add("phone", phone)
+ .add("email", email)
+ .add("address", address)
+ .add("birthday", birthday)
+ .add("free times", schedule)
+ .add("tags", tags)
+ .toString();
+ }
+
+}
+
diff --git a/src/main/java/seedu/address/logic/commands/edit/EditUserCommand.java b/src/main/java/seedu/address/logic/commands/edit/EditUserCommand.java
new file mode 100644
index 00000000000..118d50608a0
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/edit/EditUserCommand.java
@@ -0,0 +1,134 @@
+package seedu.address.logic.commands.edit;
+
+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_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+
+import java.util.ArrayList;
+import java.util.Set;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.Birthday;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Phone;
+import seedu.address.model.person.timetable.DatedEvent;
+import seedu.address.model.person.timetable.Schedule;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.user.User;
+
+
+/**
+ * Edits the details of the user of the address book.
+ */
+public class EditUserCommand extends Command {
+
+ public static final String COMMAND_WORD = "user";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Edits your details. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "Parameters: "
+ + "[" + PREFIX_NAME + "NAME] "
+ + "[" + PREFIX_PHONE + "PHONE] "
+ + "[" + PREFIX_EMAIL + "EMAIL] "
+ + "[" + PREFIX_ADDRESS + "ADDRESS] "
+ + "[" + PREFIX_BIRTHDAY + "BIRTHDAY]...\n"
+ + "[" + PREFIX_TAG + "TAG]...\n"
+ + "Example: " + COMMAND_WORD
+ + PREFIX_PHONE + "91234567 "
+ + PREFIX_EMAIL + "johndoe@example.com";
+
+ public static final String MESSAGE_EDIT_USER_SUCCESS = "Edited Your Details: %1$s";
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+
+ public static final String MESSAGE_DUPLICATE_USER = "No changes to user.";
+ public static final String MESSAGE_DUPLICATE_PHONE = "This phone number already exists in the address book.";
+ public static final String MESSAGE_DUPLICATE_EMAIL = "This email already exists in the address book.";
+
+ private final EditUserDescriptor editUserDescriptor;
+
+ /**
+ * @param editUserDescriptor details to edit the user with
+ */
+ public EditUserCommand(EditUserDescriptor editUserDescriptor) {
+ requireNonNull(editUserDescriptor);
+
+ this.editUserDescriptor = new EditUserDescriptor(editUserDescriptor);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ User userToEdit = model.getUser();
+ User editedUser = createEditedUser(userToEdit, editUserDescriptor);
+
+ if (userToEdit.equals(editedUser)) {
+ throw new CommandException(MESSAGE_DUPLICATE_USER);
+ }
+
+ if (!userToEdit.isSamePhone(editedUser) && model.hasPhone(editedUser)) {
+ throw new CommandException(MESSAGE_DUPLICATE_PHONE);
+ }
+
+ if (!userToEdit.isSameEmail(editedUser) && model.hasEmail(editedUser)) {
+ throw new CommandException(MESSAGE_DUPLICATE_EMAIL);
+ }
+
+ model.setUser(editedUser);
+ return new CommandResult(String.format(MESSAGE_EDIT_USER_SUCCESS,
+ Messages.format(editedUser)), false, false, true, false);
+ }
+
+ /**
+ * Creates and returns a {@code Person} with the details of {@code userToEdit}
+ * edited with {@code editUserDescriptor}.
+ */
+ private static User createEditedUser(User userToEdit, EditUserDescriptor editUserDescriptor) {
+ assert userToEdit != null;
+ Name updatedName = editUserDescriptor.getName().orElse(userToEdit.getName());
+ Phone updatedPhone = editUserDescriptor.getPhone().orElse(userToEdit.getPhone());
+ Email updatedEmail = editUserDescriptor.getEmail().orElse(userToEdit.getEmail());
+ Address updatedAddress = editUserDescriptor.getAddress().orElse(userToEdit.getAddress());
+ Birthday updatedBirthday = editUserDescriptor.getBirthday().orElse(userToEdit.getBirthday());
+ Schedule schedule = userToEdit.getSchedule();
+ Set updatedTags = editUserDescriptor.getTags().orElse(userToEdit.getTags());
+ ArrayList updatedDatedEvents = editUserDescriptor.getDatedEvents()
+ .orElse(userToEdit.getDatedEvents());
+ return new User(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedBirthday,
+ schedule, updatedTags, updatedDatedEvents);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditUserCommand)) {
+ return false;
+ }
+
+ EditUserCommand otherEditUserCommand = (EditUserCommand) other;
+ return editUserDescriptor.equals(otherEditUserCommand.editUserDescriptor);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("editUserDescriptor", editUserDescriptor)
+ .toString();
+ }
+}
+
diff --git a/src/main/java/seedu/address/logic/commands/edit/EditUserDescriptor.java b/src/main/java/seedu/address/logic/commands/edit/EditUserDescriptor.java
new file mode 100644
index 00000000000..9eb110728b6
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/edit/EditUserDescriptor.java
@@ -0,0 +1,95 @@
+package seedu.address.logic.commands.edit;
+
+import java.util.ArrayList;
+import java.util.Optional;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.person.timetable.DatedEvent;
+
+/**
+ * EditUserDescriptor class extends EditPersonDescriptor and represents a descriptor for editing a user.
+ * It contains an ArrayList of DatedEvent objects representing the user's dated events.
+ */
+public class EditUserDescriptor extends EditPersonDescriptor {
+ private ArrayList datedEvents;
+
+ /**
+ * Represents the descriptor for editing a user.
+ * Contains the fields to be edited and their new values.
+ */
+ public EditUserDescriptor() {
+ super();
+ datedEvents = new ArrayList<>();
+ }
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code tags} is used internally.
+ */
+ public EditUserDescriptor(EditUserDescriptor toCopy) {
+ super(toCopy);
+ setDatedEvents(toCopy.datedEvents);
+ }
+
+ /**
+ * Returns an optional list of dated events of the user.
+ * If the list is null, returns an empty optional.
+ *
+ * @return An optional list of dated events of the user.
+ */
+ public Optional> getDatedEvents() {
+ return Optional.ofNullable(datedEvents);
+ }
+
+ /**
+ * Sets the list of dated events for the user to the given list of dated events.
+ * @param datedEvents The list of dated events to set for the user.
+ */
+ public void setDatedEvents(ArrayList datedEvents) {
+ this.datedEvents = datedEvents;
+ }
+
+ /**
+ * Returns true if the given object is equal to this EditUserDescriptor object.
+ * Two EditUserDescriptor objects are considered equal if they have the same
+ * datedEvents and if their super classes are equal.
+ *
+ * @param other The object to compare with this EditUserDescriptor object.
+ * @return True if the given object is equal to this EditUserDescriptor object, false otherwise.
+ */
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditUserDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditUserDescriptor e = (EditUserDescriptor) other;
+
+ return super.equals(e)
+ && datedEvents.equals(e.datedEvents);
+ }
+
+ /**
+ * Returns a string representation of the EditUserDescriptor object.
+ * Includes the name, phone, email, address, birthday, free times, tags, and dated events.
+ *
+ * @return String representation of the EditUserDescriptor object.
+ */
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("name", super.getName())
+ .add("phone", super.getPhone())
+ .add("email", super.getEmail())
+ .add("address", super.getAddress())
+ .add("birthday", super.getBirthday())
+ .add("free times", super.getSchedule())
+ .add("tags", super.getTags())
+ .add("dated events", datedEvents)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
index 4ff1a97ed77..65b0d03bad5 100644
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
@@ -1,22 +1,25 @@
package seedu.address.logic.parser;
-import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
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_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Birthday;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.timetable.Schedule;
import seedu.address.model.tag.Tag;
/**
@@ -31,21 +34,25 @@ public class AddCommandParser implements Parser {
*/
public AddCommand parse(String args) throws ParseException {
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentTokenizer.tokenize(
+ args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS,
+ PREFIX_BIRTHDAY, PREFIX_TAG);
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
- || !argMultimap.getPreamble().isEmpty()) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
- }
+ checkPresentPrefixes(argMultimap);
+ checkUniquePrefixes(argMultimap);
+
+ // Check if all prefixes are unique
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE,
+ PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_BIRTHDAY);
- argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
+ Birthday birthday = ParserUtil.parseBirthday(argMultimap.getValue(PREFIX_BIRTHDAY).get());
Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
-
- Person person = new Person(name, phone, email, address, tagList);
+ Schedule schedule = new Schedule();
+ Person person = new Person(name, phone, email, address, birthday, schedule, tagList);
return new AddCommand(person);
}
@@ -58,4 +65,73 @@ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Pre
return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
}
+ /**
+ * Checks if the required prefixes for adding an event are present in the given argument multimap.
+ * Throws a ParseException if any of the required prefixes are missing.
+ *
+ * @param argumentMultimap The argument multimap to check for the presence of prefixes.
+ * @throws ParseException If any of the required prefixes are missing.
+ */
+ private static void checkPresentPrefixes(ArgumentMultimap argumentMultimap) throws ParseException {
+ if (!arePrefixesPresent(argumentMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS,
+ PREFIX_BIRTHDAY)) {
+ List missingPrefix = getMissingPrefixes(argumentMultimap, PREFIX_NAME,
+ PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS,
+ PREFIX_BIRTHDAY);
+ String missingPrefixString = "";
+ for (Prefix prefix : missingPrefix) {
+ missingPrefixString += prefix + " ";
+ }
+ throw new ParseException(String.format("Missing prefix(es) for %s!\n"
+ + "Message Usage:\n" + AddCommand.MESSAGE_USAGE, missingPrefixString));
+ }
+ }
+
+ /**
+ * Returns the prefixes that is not present in the given {@code ArgumentMultimap}.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ private static List getMissingPrefixes(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).filter(prefix -> argumentMultimap.getValue(prefix).isEmpty())
+ .collect(java.util.stream.Collectors.toList());
+ }
+
+ /**
+ * Returns true if there are duplicate prefixes
+ * @param argumentMultimap
+ * @param prefixes
+ */
+ private static boolean arePrefixesUnique(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getAllValues(prefix).size() == 1);
+ }
+
+ /**
+ * Checks if the prefixes for event name, schedule and reminder are unique in the given ArgumentMultimap.
+ * Throws a ParseException if there are duplicate prefixes.
+ *
+ * @param argumentMultimap the ArgumentMultimap to check for duplicate prefixes
+ * @throws ParseException if there are duplicate prefixes
+ */
+ private static void checkUniquePrefixes(ArgumentMultimap argumentMultimap) throws ParseException {
+ if (!arePrefixesUnique(argumentMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS,
+ PREFIX_BIRTHDAY)) {
+ List duplicatePrefix = getDuplicatePrefixes(argumentMultimap, PREFIX_NAME,
+ PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS,
+ PREFIX_BIRTHDAY);
+ String duplicatePrefixString = "";
+ for (Prefix prefix : duplicatePrefix) {
+ duplicatePrefixString += prefix + " ";
+ }
+ throw new ParseException(String.format("You can only have 1 of each prefix!\n"
+ + "Duplicated prefixes are: " + duplicatePrefixString));
+ }
+ }
+
+ /**
+ * Returns the prefixes that are not unique in the given {@code ArgumentMultimap}.
+ */
+ private static List getDuplicatePrefixes(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).filter(prefix -> argumentMultimap.getAllValues(prefix).size() > 1)
+ .collect(java.util.stream.Collectors.toList());
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/AddEventCommandParser.java b/src/main/java/seedu/address/logic/parser/AddEventCommandParser.java
new file mode 100644
index 00000000000..8aaec4e7577
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddEventCommandParser.java
@@ -0,0 +1,130 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EVENTNAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_REMINDER;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SCHEDULE;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.AddEventCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new AddEventCommand object.
+ */
+public class AddEventCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddEventCommand
+ * and returns an AddEventCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ @Override
+ public AddEventCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_EVENTNAME,
+ PREFIX_SCHEDULE, PREFIX_REMINDER);
+
+ checkPresentPrefixes(argMultimap);
+ checkUniquePrefixes(argMultimap);
+
+ String indexString;
+
+ String eventName = argMultimap.getValue(PREFIX_EVENTNAME).get().toUpperCase();
+ String schedule = argMultimap.getValue(PREFIX_SCHEDULE).get();
+ String reminder = argMultimap.getValue(PREFIX_REMINDER).get();
+
+ indexString = argMultimap.getPreamble().toLowerCase();
+ if (indexString.equals("user")) {
+ return new AddEventCommand(eventName, schedule, reminder);
+ } else {
+ try {
+ Integer.parseInt(indexString);
+ return new AddEventCommand(eventName, ParserUtil.parseIndex(indexString), schedule, reminder);
+ } catch (NumberFormatException e) {
+ throw new ParseException(String.format("Invalid index!" + "\n"
+ + "Index can only be 'user' or a positive integer! \n"));
+ }
+ }
+ }
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+ /**
+ * Checks if the required prefixes for adding an event are present in the given argument multimap.
+ * Throws a ParseException if any of the required prefixes are missing.
+ *
+ * @param argumentMultimap The argument multimap to check for the presence of prefixes.
+ * @throws ParseException If any of the required prefixes are missing.
+ */
+ private static void checkPresentPrefixes(ArgumentMultimap argumentMultimap) throws ParseException {
+ if (!arePrefixesPresent(argumentMultimap, PREFIX_EVENTNAME,
+ PREFIX_SCHEDULE, PREFIX_REMINDER)) {
+ List missingPrefix = getMissingPrefixes(argumentMultimap, PREFIX_EVENTNAME,
+ PREFIX_SCHEDULE, PREFIX_REMINDER);
+ String missingPrefixString = "";
+ for (Prefix prefix : missingPrefix) {
+ missingPrefixString += prefix + " ";
+ }
+ throw new ParseException(String.format("Missing prefix(es) for %s!\n"
+ + "Message Usage:\n" + AddEventCommand.MESSAGE_USAGE, missingPrefixString));
+ }
+ }
+
+ /**
+ * Returns the prefixes that is not present in the given {@code ArgumentMultimap}.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ private static List getMissingPrefixes(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).filter(prefix -> argumentMultimap.getValue(prefix).isEmpty())
+ .collect(java.util.stream.Collectors.toList());
+ }
+
+ /**
+ * Returns true if there are duplicate prefixes
+ * @param argumentMultimap
+ * @param prefixes
+ */
+ private static boolean arePrefixesUnique(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getAllValues(prefix).size() == 1);
+ }
+
+ /**
+ * Checks if the prefixes for event name, schedule and reminder are unique in the given ArgumentMultimap.
+ * Throws a ParseException if there are duplicate prefixes.
+ *
+ * @param argumentMultimap the ArgumentMultimap to check for duplicate prefixes
+ * @throws ParseException if there are duplicate prefixes
+ */
+ private static void checkUniquePrefixes(ArgumentMultimap argumentMultimap) throws ParseException {
+ if (!arePrefixesUnique(argumentMultimap, PREFIX_EVENTNAME,
+ PREFIX_SCHEDULE, PREFIX_REMINDER)) {
+ List duplicatePrefix = getDuplicatePrefixes(argumentMultimap, PREFIX_EVENTNAME,
+ PREFIX_SCHEDULE, PREFIX_REMINDER);
+ String duplicatePrefixString = "";
+ for (Prefix prefix : duplicatePrefix) {
+ duplicatePrefixString += prefix + " ";
+ }
+ throw new ParseException(String.format("You can only have 1 of each prefix!\n"
+ + "Duplicated prefixes are: " + duplicatePrefixString));
+ }
+ }
+
+ /**
+ * Returns the prefixes that are not unique in the given {@code ArgumentMultimap}.
+ */
+ private static List getDuplicatePrefixes(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).filter(prefix -> argumentMultimap.getAllValues(prefix).size() > 1)
+ .collect(java.util.stream.Collectors.toList());
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddScheduleCommandParser.java b/src/main/java/seedu/address/logic/parser/AddScheduleCommandParser.java
new file mode 100644
index 00000000000..12f43051e6d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddScheduleCommandParser.java
@@ -0,0 +1,126 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EVENTNAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EVENTTYPE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SCHEDULE;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.AddScheduleCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new AddScheduleCommand object.
+ */
+public class AddScheduleCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddScheduleCommand
+ * and returns an AddScheduleCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ @Override
+ public AddScheduleCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_EVENTNAME, PREFIX_EVENTTYPE, PREFIX_SCHEDULE);
+
+ checkPresentPrefixes(argMultimap);
+ checkUniquePrefixes(argMultimap);
+
+ String indexString;
+
+ String eventName = argMultimap.getValue(PREFIX_EVENTNAME).get().toUpperCase();
+ String eventType = argMultimap.getValue(PREFIX_EVENTTYPE).get().toLowerCase();
+ String schedule = argMultimap.getValue(PREFIX_SCHEDULE).get();
+
+ indexString = argMultimap.getPreamble().toLowerCase();
+ if (indexString.equals("user")) {
+ return new AddScheduleCommand(eventName, eventType, schedule);
+ } else {
+ try {
+ Integer.parseInt(indexString);
+ return new AddScheduleCommand(eventName, eventType,
+ ParserUtil.parseIndex(indexString), schedule);
+ } catch (NumberFormatException e) {
+ throw new ParseException("Invalid index!" + "\n"
+ + "Index can only be 'user' or a positive integer! \n");
+ }
+ }
+ }
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+ /**
+ * Checks if the required prefixes for adding an event are present in the given argument multimap.
+ * Throws a ParseException if any of the required prefixes are missing.
+ * @param argMultimap The argument multimap to check for the presence of prefixes.
+ * @throws ParseException If any of the required prefixes are missing.
+ */
+ private static void checkPresentPrefixes(ArgumentMultimap argMultimap) throws ParseException {
+ if (!arePrefixesPresent(argMultimap, PREFIX_EVENTNAME, PREFIX_EVENTTYPE, PREFIX_SCHEDULE)) {
+ List missingPrefix = getMissingPrefixes(argMultimap, PREFIX_EVENTNAME,
+ PREFIX_EVENTTYPE, PREFIX_SCHEDULE);
+ String missingPrefixString = "";
+ for (Prefix prefix : missingPrefix) {
+ missingPrefixString += prefix + " ";
+ }
+ throw new ParseException(String.format("Missing prefix(es) for %s!\n"
+ + "Message Usage:\n" + AddScheduleCommand.MESSAGE_USAGE, missingPrefixString));
+ }
+ }
+
+ /**
+ * Returns the prefixes that is not present in the given {@code ArgumentMultimap}.
+ */
+ private static List getMissingPrefixes(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).filter(prefix -> argumentMultimap.getValue(prefix).isEmpty())
+ .collect(java.util.stream.Collectors.toList());
+ }
+
+ /**
+ * Returns true if there are duplicate prefixes
+ * @param argumentMultimap The argument multimap to check for duplicate prefixes.
+ * @param prefixes The prefixes to check for duplicates.
+ */
+ private static boolean arePrefixesUnique(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getAllValues(prefix).size() == 1);
+ }
+
+ /**
+ * Checks if there are duplicate prefixes in the given argument multimap.
+ * Throws a ParseException if there are duplicate prefixes.
+ * @param argMultimap The argument multimap to check for duplicate prefixes.
+ * @throws ParseException If there are duplicate prefixes.
+ */
+ private static void checkUniquePrefixes(ArgumentMultimap argMultimap) throws ParseException {
+ if (!arePrefixesUnique(argMultimap, PREFIX_EVENTNAME,
+ PREFIX_EVENTTYPE, PREFIX_SCHEDULE)) {
+ List duplicatePrefix = getDuplicatePrefixes(argMultimap, PREFIX_EVENTNAME,
+ PREFIX_EVENTTYPE, PREFIX_SCHEDULE);
+ String duplicatePrefixString = "";
+ for (Prefix prefix : duplicatePrefix) {
+ duplicatePrefixString += prefix + " ";
+ }
+ throw new ParseException(String.format("You can only have 1 of each prefix!\n"
+ + "Duplicated prefixes are: " + duplicatePrefixString));
+ }
+ }
+
+ /**
+ * Returns the prefixes that are not unique in the given {@code ArgumentMultimap}.
+ */
+ private static List getDuplicatePrefixes(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).filter(prefix -> argumentMultimap.getAllValues(prefix).size() > 1)
+ .collect(java.util.stream.Collectors.toList());
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
index 3149ee07e0b..9dd383c3dd5 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -9,14 +9,22 @@
import seedu.address.commons.core.LogsCenter;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.AddEventCommand;
+import seedu.address.logic.commands.AddScheduleCommand;
import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.Command;
+import seedu.address.logic.commands.CommonFreetimeCommand;
import seedu.address.logic.commands.DeleteCommand;
-import seedu.address.logic.commands.EditCommand;
import seedu.address.logic.commands.ExitCommand;
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.RemoveEventCommand;
+import seedu.address.logic.commands.RemoveReminderCommand;
+import seedu.address.logic.commands.RemoveScheduleCommand;
+import seedu.address.logic.commands.SetReminderCommand;
+import seedu.address.logic.commands.edit.EditCommand;
+import seedu.address.logic.commands.edit.EditUserCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -77,6 +85,30 @@ public Command parseCommand(String userInput) throws ParseException {
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case EditUserCommand.COMMAND_WORD:
+ return new EditUserCommandParser().parse(arguments);
+
+ case SetReminderCommand.COMMAND_WORD:
+ return new SetReminderCommandParser().parse(arguments);
+
+ case RemoveReminderCommand.COMMAND_WORD:
+ return new RemoveReminderCommandParser().parse(arguments);
+
+ case CommonFreetimeCommand.COMMAND_WORD:
+ return new CommonFreeTimeCommandParser().parse(arguments);
+
+ case AddEventCommand.COMMAND_WORD:
+ return new AddEventCommandParser().parse(arguments);
+
+ case RemoveEventCommand.COMMAND_WORD:
+ return new RemoveEventCommandParser().parse(arguments);
+
+ case AddScheduleCommand.COMMAND_WORD:
+ return new AddScheduleCommandParser().parse(arguments);
+
+ case RemoveScheduleCommand.COMMAND_WORD:
+ return new RemoveScheduleCommandParser().parse(arguments);
+
default:
logger.finer("This user input caused a ParseException: " + userInput);
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..770c3f50b9a 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -10,6 +10,10 @@ public class CliSyntax {
public static final Prefix PREFIX_PHONE = new Prefix("p/");
public static final Prefix PREFIX_EMAIL = new Prefix("e/");
public static final Prefix PREFIX_ADDRESS = new Prefix("a/");
+ public static final Prefix PREFIX_BIRTHDAY = new Prefix("b/");
+ public static final Prefix PREFIX_SCHEDULE = new Prefix("h/");
public static final Prefix PREFIX_TAG = new Prefix("t/");
-
+ public static final Prefix PREFIX_EVENTNAME = new Prefix("en/");
+ public static final Prefix PREFIX_EVENTTYPE = new Prefix("type/");
+ public static final Prefix PREFIX_REMINDER = new Prefix("r/");
}
diff --git a/src/main/java/seedu/address/logic/parser/CommonFreeTimeCommandParser.java b/src/main/java/seedu/address/logic/parser/CommonFreeTimeCommandParser.java
new file mode 100644
index 00000000000..d7112037153
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/CommonFreeTimeCommandParser.java
@@ -0,0 +1,44 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.CommonFreetimeCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new CommonFreetimeCommand object.
+ */
+public class CommonFreeTimeCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the CommonFreetimeCommand
+ * and returns a CommonFreetimeCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ @Override
+ public CommonFreetimeCommand parse(String userInput) throws ParseException {
+ requireNonNull(userInput);
+
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(userInput, PREFIX_NAME);
+
+ Index index;
+ // Check if preamble is present
+ try {
+ if (userInput.equalsIgnoreCase("")) {
+ return new CommonFreetimeCommand();
+ } else {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ }
+ } catch (Exception e) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ CommonFreetimeCommand.MESSAGE_USAGE));
+ }
+
+ return new CommonFreetimeCommand(index);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
index 46b3309a78b..cfcaef12add 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java
@@ -3,9 +3,11 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
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_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_SCHEDULE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import java.util.Collection;
@@ -14,8 +16,8 @@
import java.util.Set;
import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
+import seedu.address.logic.commands.edit.EditCommand;
+import seedu.address.logic.commands.edit.EditPersonDescriptor;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.tag.Tag;
@@ -32,20 +34,25 @@ public class EditCommandParser implements Parser {
public EditCommand parse(String args) throws ParseException {
requireNonNull(args);
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS,
+ PREFIX_BIRTHDAY, PREFIX_SCHEDULE, PREFIX_TAG);
Index index;
+ // Check if preamble is present
try {
index = ParserUtil.parseIndex(argMultimap.getPreamble());
} catch (ParseException pe) {
throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
}
- argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
+ // Check if all prefixes are unique
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE,
+ PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_BIRTHDAY);
EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
+ // Check if all present prefixes are valid
if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()));
}
@@ -58,6 +65,9 @@ public EditCommand parse(String args) throws ParseException {
if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) {
editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
}
+ if (argMultimap.getValue(PREFIX_BIRTHDAY).isPresent()) {
+ editPersonDescriptor.setBirthday(ParserUtil.parseBirthday(argMultimap.getValue(PREFIX_BIRTHDAY).get()));
+ }
parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
if (!editPersonDescriptor.isAnyFieldEdited()) {
diff --git a/src/main/java/seedu/address/logic/parser/EditUserCommandParser.java b/src/main/java/seedu/address/logic/parser/EditUserCommandParser.java
new file mode 100644
index 00000000000..383d73f5b54
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/EditUserCommandParser.java
@@ -0,0 +1,89 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+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_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.address.logic.commands.edit.EditUserCommand;
+import seedu.address.logic.commands.edit.EditUserDescriptor;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Parses input arguments and creates a new EditCommand object
+ */
+public class EditUserCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EditCommand
+ * and returns an EditCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EditUserCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS,
+ PREFIX_BIRTHDAY, PREFIX_TAG);
+
+ // Check if preamble is present
+ if (!argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditUserCommand.MESSAGE_USAGE));
+ }
+
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE,
+ PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_BIRTHDAY);
+
+ EditUserDescriptor editUserDescriptor = new EditUserDescriptor();
+
+ // Check if all present prefixes are valid
+ if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
+ editUserDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()));
+ }
+ if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
+ editUserDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()));
+ }
+ if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) {
+ editUserDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()));
+ }
+ if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) {
+ editUserDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
+ }
+ if (argMultimap.getValue(PREFIX_BIRTHDAY).isPresent()) {
+ editUserDescriptor.setBirthday(ParserUtil.parseBirthday(argMultimap.getValue(PREFIX_BIRTHDAY).get()));
+ }
+ parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editUserDescriptor::setTags);
+
+ if (!editUserDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditUserCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EditUserCommand(editUserDescriptor);
+ }
+
+ /**
+ * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty.
+ * If {@code tags} contain only one element which is an empty string, it will be parsed into a
+ * {@code Set} containing zero tags.
+ */
+ private Optional> parseTagsForEdit(Collection tags) throws ParseException {
+ assert tags != null;
+
+ if (tags.isEmpty()) {
+ return Optional.empty();
+ }
+ Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags;
+ return Optional.of(ParserUtil.parseTags(tagSet));
+ }
+
+}
+
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..27c42173a7c 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -10,6 +10,7 @@
import seedu.address.commons.util.StringUtil;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Birthday;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
@@ -47,7 +48,21 @@ public static Name parseName(String name) throws ParseException {
if (!Name.isValidName(trimmedName)) {
throw new ParseException(Name.MESSAGE_CONSTRAINTS);
}
- return new Name(trimmedName);
+
+ // Split the name into words based on spaces
+ String[] words = trimmedName.split("\\s+");
+ StringBuilder capitalizedBuilder = new StringBuilder();
+
+ for (String word : words) {
+ // Capitalize the first letter of each word and lowercase the rest of the letters
+ String capitalizedWord = word.substring(0, 1).toUpperCase()
+ + word.substring(1).toLowerCase();
+ capitalizedBuilder.append(capitalizedWord).append(" ");
+ }
+
+ // Trim the trailing space
+ String capitalizedTrimmedName = capitalizedBuilder.toString().trim();
+ return new Name(capitalizedTrimmedName);
}
/**
@@ -59,6 +74,7 @@ public static Name parseName(String name) throws ParseException {
public static Phone parsePhone(String phone) throws ParseException {
requireNonNull(phone);
String trimmedPhone = phone.trim();
+
if (!Phone.isValidPhone(trimmedPhone)) {
throw new ParseException(Phone.MESSAGE_CONSTRAINTS);
}
@@ -74,6 +90,7 @@ public static Phone parsePhone(String phone) throws ParseException {
public static Address parseAddress(String address) throws ParseException {
requireNonNull(address);
String trimmedAddress = address.trim();
+
if (!Address.isValidAddress(trimmedAddress)) {
throw new ParseException(Address.MESSAGE_CONSTRAINTS);
}
@@ -89,12 +106,25 @@ public static Address parseAddress(String address) throws ParseException {
public static Email parseEmail(String email) throws ParseException {
requireNonNull(email);
String trimmedEmail = email.trim();
+
if (!Email.isValidEmail(trimmedEmail)) {
throw new ParseException(Email.MESSAGE_CONSTRAINTS);
}
return new Email(trimmedEmail);
}
+ /**
+ * Parses a {@code String birthday} into a {@code Birthday}.
+ */
+ public static Birthday parseBirthday(String date) throws ParseException {
+ requireNonNull(date);
+ String trimmedDate = date.trim();
+ if (!Birthday.isValidBirthday(trimmedDate)) {
+ throw new ParseException(Birthday.MESSAGE_CONSTRAINTS);
+ }
+ return new Birthday(date);
+ }
+
/**
* Parses a {@code String tag} into a {@code Tag}.
* Leading and trailing whitespaces will be trimmed.
@@ -121,4 +151,5 @@ public static Set parseTags(Collection tags) throws ParseException
}
return tagSet;
}
+
}
diff --git a/src/main/java/seedu/address/logic/parser/RemoveEventCommandParser.java b/src/main/java/seedu/address/logic/parser/RemoveEventCommandParser.java
new file mode 100644
index 00000000000..7638ee88c97
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/RemoveEventCommandParser.java
@@ -0,0 +1,96 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EVENTNAME;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.RemoveEventCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteEventCommand object
+ */
+public class RemoveEventCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteEventCommand
+ * and returns a DeleteEventCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public RemoveEventCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_EVENTNAME);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_EVENTNAME)) {
+ List missingPrefix = getMissingPrefixes(argMultimap, PREFIX_EVENTNAME);
+ String missingPrefixString = "";
+ for (Prefix prefix : missingPrefix) {
+ missingPrefixString += prefix + " ";
+ }
+ throw new ParseException(String.format("Missing prefix(es) for %s!\n"
+ + "Message Usage:\n" + RemoveEventCommand.MESSAGE_USAGE, missingPrefixString));
+ }
+
+
+ if (!arePrefixesUnique(argMultimap, PREFIX_EVENTNAME)) {
+ List duplicatePrefix = getDuplicatePrefixes(argMultimap, PREFIX_EVENTNAME);
+ String duplicatePrefixString = "";
+ for (Prefix prefix : duplicatePrefix) {
+ duplicatePrefixString += prefix + " ";
+ }
+ throw new ParseException(String.format("You can only have 1 of each prefix!\n"
+ + "Duplicated prefixes are: " + duplicatePrefixString));
+ }
+
+ String eventName = argMultimap.getValue(PREFIX_EVENTNAME).get().toUpperCase();
+ String indexString = argMultimap.getPreamble().toLowerCase();
+ if (indexString.equals("user")) {
+ return new RemoveEventCommand(eventName, null);
+ } else {
+ try {
+ Integer.parseInt(indexString);
+ return new RemoveEventCommand(eventName, ParserUtil.parseIndex(indexString));
+ } catch (NumberFormatException e) {
+ throw new ParseException("Invalid index!" + "\n"
+ + "Index can only be 'user' or a positive integer! \n");
+ }
+ }
+
+ }
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+
+ /**
+ * Returns true if there are duplicate prefixes
+ * @param argumentMultimap The argument multimap to check for the presence of prefixes.
+ * @param prefixes The prefixes to check for duplicates.
+ */
+ private static boolean arePrefixesUnique(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getAllValues(prefix).size() == 1);
+ }
+
+ /**
+ * Returns the prefixes that is not present in the given {@code ArgumentMultimap}.
+ */
+ private static List getMissingPrefixes(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).filter(prefix -> argumentMultimap.getValue(prefix).isEmpty())
+ .collect(java.util.stream.Collectors.toList());
+ }
+
+ /**
+ * Returns the prefixes that are not unique in the given {@code ArgumentMultimap}.
+ */
+ private static List getDuplicatePrefixes(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).filter(prefix -> argumentMultimap.getAllValues(prefix).size() > 1)
+ .collect(java.util.stream.Collectors.toList());
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/RemoveReminderCommandParser.java b/src/main/java/seedu/address/logic/parser/RemoveReminderCommandParser.java
new file mode 100644
index 00000000000..d16292b96dd
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/RemoveReminderCommandParser.java
@@ -0,0 +1,27 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.logic.commands.RemoveReminderCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new RemoveReminderCommand object
+ */
+public class RemoveReminderCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the RemoveReminderCommand
+ * and returns a RemoveReminderCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public RemoveReminderCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveReminderCommand.MESSAGE_USAGE));
+ }
+
+ return new RemoveReminderCommand(trimmedArgs);
+ }
+}
+
diff --git a/src/main/java/seedu/address/logic/parser/RemoveScheduleCommandParser.java b/src/main/java/seedu/address/logic/parser/RemoveScheduleCommandParser.java
new file mode 100644
index 00000000000..bb2c8daa519
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/RemoveScheduleCommandParser.java
@@ -0,0 +1,97 @@
+package seedu.address.logic.parser;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EVENTNAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EVENTTYPE;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.RemoveScheduleCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteEventCommand object
+ */
+public class RemoveScheduleCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteEventCommand
+ * and returns a DeleteEventCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public RemoveScheduleCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_EVENTNAME, PREFIX_EVENTTYPE);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_EVENTNAME, PREFIX_EVENTTYPE)) {
+ List missingPrefix = getMissingPrefixes(argMultimap, PREFIX_EVENTNAME, PREFIX_EVENTTYPE);
+ String missingPrefixString = "";
+ for (Prefix prefix : missingPrefix) {
+ missingPrefixString += prefix + " ";
+ }
+ throw new ParseException(String.format("Missing prefix(es) for %s!\n"
+ + "Message Usage:\n" + RemoveScheduleCommand.MESSAGE_USAGE, missingPrefixString));
+ }
+
+ if (!arePrefixesUnique(argMultimap, PREFIX_EVENTNAME, PREFIX_EVENTTYPE)) {
+ List duplicatePrefix = getDuplicatePrefixes(argMultimap, PREFIX_EVENTNAME, PREFIX_EVENTTYPE);
+ String duplicatePrefixString = "";
+ for (Prefix prefix : duplicatePrefix) {
+ duplicatePrefixString += prefix + " ";
+ }
+ throw new ParseException(String.format("You can only have 1 of each prefix!\n"
+ + "Duplicated prefixes are: " + duplicatePrefixString));
+ }
+
+ String eventName = argMultimap.getValue(PREFIX_EVENTNAME).get().toUpperCase();
+ String eventType = argMultimap.getValue(PREFIX_EVENTTYPE).get().toLowerCase();
+ String indexString = argMultimap.getPreamble().toLowerCase();
+ if (indexString.equals("user")) {
+ return new RemoveScheduleCommand(eventName, eventType, null);
+ } else {
+ try {
+ Integer.parseInt(indexString);
+ return new RemoveScheduleCommand(eventName, eventType,
+ ParserUtil.parseIndex(indexString));
+ } catch (NumberFormatException e) {
+ throw new ParseException("Invalid index!" + "\n"
+ + "Index can only be 'user' or a positive integer! \n");
+ }
+ }
+ }
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+ /**
+ * Returns true if there are duplicate prefixes
+ * @param argumentMultimap The argument multimap to check for the presence of prefixes.
+ * @param prefixes The prefixes to check for duplicates.
+ */
+ private static boolean arePrefixesUnique(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getAllValues(prefix).size() == 1);
+ }
+
+ /**
+ * Returns the prefixes that is not present in the given {@code ArgumentMultimap}.
+ */
+ private static List getMissingPrefixes(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).filter(prefix -> argumentMultimap.getValue(prefix).isEmpty())
+ .collect(java.util.stream.Collectors.toList());
+ }
+
+ /**
+ * Returns the prefixes that are not unique in the given {@code ArgumentMultimap}.
+ */
+ private static List getDuplicatePrefixes(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).filter(prefix -> argumentMultimap.getAllValues(prefix).size() > 1)
+ .collect(java.util.stream.Collectors.toList());
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/SetReminderCommandParser.java b/src/main/java/seedu/address/logic/parser/SetReminderCommandParser.java
new file mode 100644
index 00000000000..5e4ee15d58e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/SetReminderCommandParser.java
@@ -0,0 +1,26 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.logic.commands.SetReminderCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new SetReminderCommand object
+ */
+public class SetReminderCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the SetReminderCommand
+ * and returns a SetReminderCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public SetReminderCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, SetReminderCommand.MESSAGE_USAGE));
+ }
+
+ return new SetReminderCommand(trimmedArgs);
+ }
+}
diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java
index 73397161e84..95c58b7811a 100644
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ b/src/main/java/seedu/address/model/AddressBook.java
@@ -2,10 +2,13 @@
import static java.util.Objects.requireNonNull;
+import java.time.LocalDate;
import java.util.List;
+import java.util.Objects;
import javafx.collections.ObservableList;
import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.UniquePersonList;
@@ -48,6 +51,15 @@ public void setPersons(List persons) {
this.persons.setPersons(persons);
}
+ /**
+ * Returns the person with the given name
+ *
+ * @param name Name of the person to be retrieved.
+ */
+ public Person getPersonWithName(Name name) {
+ return persons.getPersonWithName(name);
+ }
+
/**
* Resets the existing data of this {@code AddressBook} with {@code newData}.
*/
@@ -67,6 +79,22 @@ public boolean hasPerson(Person person) {
return persons.contains(person);
}
+ /**
+ * Returns true if a person with the same phone number as {@code person} exists in the address book.
+ */
+ public boolean hasPhone(Person person) {
+ requireNonNull(person);
+ return persons.containsPhone(person);
+ }
+
+ /**
+ * Returns true if a person with the same email address as {@code person} exists in the address book.
+ */
+ public boolean hasEmail(Person person) {
+ requireNonNull(person);
+ return persons.containsEmail(person);
+ }
+
/**
* Adds a person to the address book.
* The person must not already exist in the address book.
@@ -94,6 +122,17 @@ public void removePerson(Person key) {
persons.remove(key);
}
+ public String getBirthdayList() {
+ StringBuilder sb = new StringBuilder();
+ for (Person person: persons) {
+ if (person.getBirthday().getDate().getMonth().equals(LocalDate.now().getMonth())
+ && Objects.equals(person.getBirthday().getDate().getDayOfMonth(), LocalDate.now().getDayOfMonth())) {
+ sb.append(person.getName() + "\n");
+ }
+ }
+ return sb.toString();
+ }
+
//// util methods
@Override
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..196a6a99fa5 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -5,7 +5,11 @@
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
+import seedu.address.model.user.ReadOnlyUserData;
+import seedu.address.model.user.ReadOnlyUserPrefs;
+import seedu.address.model.user.User;
/**
* The API of the Model component.
@@ -14,6 +18,8 @@ public interface Model {
/** {@code Predicate} that always evaluate to true */
Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
+ Predicate PREDICATE_SHOW_USER = user -> true;
+
/**
* Replaces user prefs data with the data in {@code userPrefs}.
*/
@@ -52,11 +58,42 @@ public interface Model {
/** Returns the AddressBook */
ReadOnlyAddressBook getAddressBook();
+ /**
+ * Replaces user data with the data in {@code userData}.
+ */
+ void setUserData(ReadOnlyUserData userData);
+
+ /**
+ * Returns the user data.
+ */
+ ReadOnlyUserData getUserData();
+
+ /**
+ * Returns the user.
+ */
+ User getUser();
+
+ /**
+ * Sets the user.
+ * @param user
+ */
+ void setUser(User user);
+
/**
* Returns true if a person with the same identity as {@code person} exists in the address book.
*/
boolean hasPerson(Person person);
+ /**
+ * Returns true if a person with the same phone number as {@code person} exists in the address book.
+ */
+ boolean hasPhone(Person person);
+
+ /**
+ * Returns true if a person with the same email address as {@code person} exists in the address book.
+ */
+ boolean hasEmail(Person person);
+
/**
* Deletes the given person.
* The person must exist in the address book.
@@ -76,9 +113,23 @@ public interface Model {
*/
void setPerson(Person target, Person editedPerson);
+ String getBirthdayList();
+
/** Returns an unmodifiable view of the filtered person list */
ObservableList getFilteredPersonList();
+ /** Returns an unmodifiable view of the user */
+ ObservableList getUserView();
+
+ /**
+ * Returns the person in the address book with the given name.
+ * The name must be an exact match (case-insensitive).
+ *
+ * @param name The name of the person to retrieve.
+ * @return The person with the given name, or null if no such person exists.
+ */
+ Person getPersonWithName(Name name);
+
/**
* Updates the filter of the filtered person list to filter by the given {@code predicate}.
* @throws NullPointerException if {@code predicate} is null.
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 57bc563fde6..cb9c6f834aa 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -11,7 +11,13 @@
import javafx.collections.transformation.FilteredList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
+import seedu.address.model.user.ReadOnlyUserData;
+import seedu.address.model.user.ReadOnlyUserPrefs;
+import seedu.address.model.user.User;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
/**
* Represents the in-memory model of the address book data.
@@ -22,22 +28,25 @@ public class ModelManager implements Model {
private final AddressBook addressBook;
private final UserPrefs userPrefs;
private final FilteredList filteredPersons;
+ private final UserData userData;
/**
* Initializes a ModelManager with the given addressBook and userPrefs.
*/
- public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) {
+ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs, ReadOnlyUserData userData) {
requireAllNonNull(addressBook, userPrefs);
- logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs);
+ logger.fine("Initializing with address book: "
+ + addressBook + " ,user prefs " + userPrefs + " and user data " + userData);
this.addressBook = new AddressBook(addressBook);
this.userPrefs = new UserPrefs(userPrefs);
filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
+ this.userData = new UserData(userData);
}
public ModelManager() {
- this(new AddressBook(), new UserPrefs());
+ this(new AddressBook(), new UserPrefs(), new UserData());
}
//=========== UserPrefs ==================================================================================
@@ -90,7 +99,19 @@ public ReadOnlyAddressBook getAddressBook() {
@Override
public boolean hasPerson(Person person) {
requireNonNull(person);
- return addressBook.hasPerson(person);
+ return addressBook.hasPerson(person) || userData.sameAsUser(person);
+ }
+
+ @Override
+ public boolean hasPhone(Person person) {
+ requireNonNull(person);
+ return addressBook.hasPhone(person) || userData.sameAsUserPhone(person);
+ }
+
+ @Override
+ public boolean hasEmail(Person person) {
+ requireNonNull(person);
+ return addressBook.hasEmail(person) || userData.sameAsUserEmail(person);
}
@Override
@@ -111,6 +132,32 @@ public void setPerson(Person target, Person editedPerson) {
addressBook.setPerson(target, editedPerson);
}
+ public String getBirthdayList() {
+ return addressBook.getBirthdayList();
+ }
+
+ //=========== UserData =============================================================
+
+ @Override
+ public void setUserData(ReadOnlyUserData userData) {
+ this.userData.resetData(userData);
+ }
+
+ @Override
+ public ReadOnlyUserData getUserData() {
+ return userData;
+ }
+
+ @Override
+ public User getUser() {
+ return userData.getUser();
+ }
+
+ @Override
+ public void setUser(User user) {
+ userData.setUser(user);
+ }
+
//=========== Filtered Person List Accessors =============================================================
/**
@@ -122,12 +169,28 @@ public ObservableList getFilteredPersonList() {
return filteredPersons;
}
+ @Override
+ public Person getPersonWithName(Name name) {
+ return addressBook.getPersonWithName(name);
+ }
+
@Override
public void updateFilteredPersonList(Predicate predicate) {
requireNonNull(predicate);
filteredPersons.setPredicate(predicate);
}
+ //=========== User View Accessors =============================================================
+
+ /**
+ * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of
+ * {@code userData}
+ */
+ @Override
+ public ObservableList getUserView() {
+ return userData.getUserView();
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -144,5 +207,4 @@ public boolean equals(Object other) {
&& userPrefs.equals(otherModelManager.userPrefs)
&& filteredPersons.equals(otherModelManager.filteredPersons);
}
-
}
diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java
index 469a2cc9a1e..a255a9d4096 100644
--- a/src/main/java/seedu/address/model/person/Address.java
+++ b/src/main/java/seedu/address/model/person/Address.java
@@ -34,6 +34,10 @@ public Address(String address) {
* Returns true if a given string is a valid email.
*/
public static boolean isValidAddress(String test) {
+ if (test == "") {
+ return true;
+ }
+
return test.matches(VALIDATION_REGEX);
}
diff --git a/src/main/java/seedu/address/model/person/Birthday.java b/src/main/java/seedu/address/model/person/Birthday.java
new file mode 100644
index 00000000000..26d376b0a2a
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/Birthday.java
@@ -0,0 +1,78 @@
+package seedu.address.model.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeParseException;
+/**
+ * Represents a Person's birthday in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidBirthday(String)}
+ */
+public class Birthday {
+ public static final String MESSAGE_CONSTRAINTS =
+ "Birthday should be in the format of YYYY-MM-DD and should be a valid date.";
+ public static final String VALIDATION_REGEX =
+ "^\\d{4}-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])$";
+ public final String date;
+
+ /**
+ * Constructs a {@code Birthday}.
+ *
+ * @param birthday A valid birthday.
+ */
+ public Birthday(String birthday) {
+ requireNonNull(birthday);
+ checkArgument(isValidBirthday(birthday), MESSAGE_CONSTRAINTS);
+ date = birthday;
+ }
+
+ /**
+ * Returns true if a given string is a valid birthday.
+ */
+ public static Boolean isValidBirthday(String test) {
+ if (test == "") {
+ return true;
+ }
+ try {
+ LocalDate.parse(test);
+ } catch (DateTimeParseException e) {
+ return false;
+ }
+
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public String toString() {
+ return date;
+ }
+
+ public LocalDate getDate() {
+ if (date == "") {
+ return LocalDate.parse("1900-01-01");
+ }
+ return LocalDate.parse(date);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ // instanceof handles nulls
+ if (!(other instanceof Birthday)) {
+ return false;
+ }
+
+ Birthday otherBirthday = (Birthday) other;
+ return date.equals(otherBirthday.date);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return date.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java
index c62e512bc29..24d4a7377e3 100644
--- a/src/main/java/seedu/address/model/person/Email.java
+++ b/src/main/java/seedu/address/model/person/Email.java
@@ -48,6 +48,10 @@ public Email(String email) {
* Returns if a given string is a valid email.
*/
public static boolean isValidEmail(String test) {
+ if (test == "") {
+ return true;
+ }
+
return test.matches(VALIDATION_REGEX);
}
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java
index 173f15b9b00..126b6494b2b 100644
--- a/src/main/java/seedu/address/model/person/Name.java
+++ b/src/main/java/seedu/address/model/person/Name.java
@@ -38,7 +38,6 @@ public static boolean isValidName(String test) {
return test.matches(VALIDATION_REGEX);
}
-
@Override
public String toString() {
return fullName;
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java
index abe8c46b535..b0d76ccba8d 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/person/Person.java
@@ -1,13 +1,17 @@
package seedu.address.model.person;
-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.HashSet;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.person.timetable.FreeTime;
+import seedu.address.model.person.timetable.Schedule;
import seedu.address.model.tag.Tag;
/**
@@ -23,20 +27,37 @@ public class Person {
// Data fields
private final Address address;
+ private final Birthday birthday;
+ private final Schedule schedule;
private final Set tags = new HashSet<>();
/**
* Every field must be present and not null.
*/
- public Person(Name name, Phone phone, Email email, Address address, Set tags) {
- requireAllNonNull(name, phone, email, address, tags);
+ public Person(Name name, Phone phone, Email email, Address address,
+ Birthday birthday, Schedule schedule, Set tags) {
+ requireNonNull(name);
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
+ this.birthday = birthday;
+ this.schedule = schedule;
this.tags.addAll(tags);
}
+ /**
+ * Constructor for Person with default values.
+ */
+ public Person() {
+ this.name = new Name("me");
+ this.phone = new Phone("00000000");
+ this.email = new Email("me@example.com");
+ this.address = new Address("Blk 436 Serangoon Gardens Street 26, #16-43");
+ this.birthday = new Birthday("2000-01-01");
+ this.schedule = new Schedule();
+ }
+
public Name getName() {
return name;
}
@@ -52,6 +73,21 @@ public Email getEmail() {
public Address getAddress() {
return address;
}
+ public Birthday getBirthday() {
+ return birthday;
+ }
+
+ public Schedule getSchedule() {
+ return schedule;
+ }
+
+ /**
+ * Returns an immutable tag set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ */
+ public List getFreeTimes() throws NullPointerException {
+ return schedule.getThisWeeksFreeTime() == null ? new ArrayList() : schedule.getThisWeeksFreeTime();
+ }
/**
* Returns an immutable tag set, which throws {@code UnsupportedOperationException}
@@ -61,6 +97,8 @@ public Set getTags() {
return Collections.unmodifiableSet(tags);
}
+ //TODO: person.getFreeTimesWith(otherPerson)
+
/**
* Returns true if both persons have the same name.
* This defines a weaker notion of equality between two persons.
@@ -71,7 +109,34 @@ public boolean isSamePerson(Person otherPerson) {
}
return otherPerson != null
- && otherPerson.getName().equals(getName());
+ && otherPerson.getName().equals(getName())
+ && otherPerson.getBirthday().equals(getBirthday());
+ }
+
+ /**
+ * Returns true if both persons have the same phone number.
+ * This defines a notion of equality between two persons.
+ */
+ public boolean isSamePhone(Person otherPerson) {
+ if (otherPerson == this) {
+ return true;
+ }
+
+ return otherPerson != null
+ && otherPerson.getPhone().equals(getPhone());
+ }
+
+ /**
+ * Returns true if both persons have the same email address.
+ * This defines a notion of equality between two persons.
+ */
+ public boolean isSameEmail(Person otherPerson) {
+ if (otherPerson == this) {
+ return true;
+ }
+
+ return otherPerson != null
+ && otherPerson.getEmail().equals(getEmail());
}
/**
@@ -94,13 +159,15 @@ public boolean equals(Object other) {
&& phone.equals(otherPerson.phone)
&& email.equals(otherPerson.email)
&& address.equals(otherPerson.address)
+ && birthday.equals(otherPerson.birthday)
+ && schedule.equals(otherPerson.schedule)
&& tags.equals(otherPerson.tags);
}
@Override
public int hashCode() {
// use this method for custom fields hashing instead of implementing your own
- return Objects.hash(name, phone, email, address, tags);
+ return Objects.hash(name, phone, email, address, birthday, schedule, tags);
}
@Override
@@ -110,6 +177,8 @@ public String toString() {
.add("phone", phone)
.add("email", email)
.add("address", address)
+ .add("birthday", birthday)
+ .add("free times", schedule)
.add("tags", tags)
.toString();
}
diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java
index d733f63d739..6d43ce968eb 100644
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ b/src/main/java/seedu/address/model/person/Phone.java
@@ -11,8 +11,8 @@ public class Phone {
public static final String MESSAGE_CONSTRAINTS =
- "Phone numbers should only contain numbers, and it should be at least 3 digits long";
- public static final String VALIDATION_REGEX = "\\d{3,}";
+ "Phone numbers should only contain numbers, and it should be 8 digits long";
+ public static final String VALIDATION_REGEX = "\\d{8}";
public final String value;
/**
@@ -30,6 +30,9 @@ public Phone(String phone) {
* Returns true if a given string is a valid phone number.
*/
public static boolean isValidPhone(String test) {
+ if (test == "") {
+ return true;
+ }
return test.matches(VALIDATION_REGEX);
}
diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java
index cc0a68d79f9..7c98d032aa7 100644
--- a/src/main/java/seedu/address/model/person/UniquePersonList.java
+++ b/src/main/java/seedu/address/model/person/UniquePersonList.java
@@ -36,6 +36,22 @@ public boolean contains(Person toCheck) {
return internalList.stream().anyMatch(toCheck::isSamePerson);
}
+ /**
+ * Returns true if the list contains an equivalent person as the given argument.
+ */
+ public boolean containsPhone(Person toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::isSamePhone);
+ }
+
+ /**
+ * Returns true if the list contains an equivalent person as the given argument.
+ */
+ public boolean containsEmail(Person toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::isSameEmail);
+ }
+
/**
* Adds a person to the list.
* The person must not already exist in the list.
@@ -48,6 +64,21 @@ public void add(Person toAdd) {
internalList.add(toAdd);
}
+ /**
+ * Returns the person with the given name.
+ *
+ * @param name Name of the person to be retrieved.
+ */
+ public Person getPersonWithName(Name name) {
+ requireNonNull(name);
+ for (Person person : internalList) {
+ if (person.getName().equals(name)) {
+ return person;
+ }
+ }
+ return null;
+ }
+
/**
* Replaces the person {@code target} in the list with {@code editedPerson}.
* {@code target} must exist in the list.
diff --git a/src/main/java/seedu/address/model/person/timetable/Cca.java b/src/main/java/seedu/address/model/person/timetable/Cca.java
new file mode 100644
index 00000000000..10b2aa8ec91
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/timetable/Cca.java
@@ -0,0 +1,130 @@
+package seedu.address.model.person.timetable;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+
+/**
+ * Represents a Co-Curricular Activity (CCA) in the address book.
+ * Guarantees: immutability.
+ */
+public class Cca extends TimeBlock {
+ public static final String MESSAGE_CONSTRAINTS =
+ "Cca name should only contain alphanumeric characters and spaces, and it should not be blank";
+ private static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*";
+ private final String ccaName;
+
+ /**
+ * Constructs a CCA object.
+ *
+ * @param name Name of the CCA.
+ * @param timeBlockString String representation of the time block.
+ */
+ public Cca(String name, String timeBlockString) {
+ super(timeBlockString);
+
+ requireNonNull(name);
+ checkArgument(isValidCcaName(name), MESSAGE_CONSTRAINTS);
+
+ this.ccaName = name;
+ }
+
+ /**
+ * Factory method to create a new CCA object.
+ *
+ * @param unparsedInput The input string containing CCA details.
+ * @return A new CCA object.
+ */
+ public static Cca newCca(String unparsedInput) throws IllegalValueException {
+ requireNonNull(unparsedInput);
+
+ // Split the unparsed input string based on the last three whitespace occurrences
+ String[] parts = unparsedInput.split("\\s+");
+
+ // Check for valid number of parts (at least 4 parts are required)
+ if (parts.length < 4) {
+ throw new IllegalArgumentException("Invalid CCA input format. Expected: CCA_NAME DAY HHMM HHMM");
+ }
+
+ // Extracting the last three parts for day, startTime, and endTime
+ String day = parts[parts.length - 3];
+ String startTime = parts[parts.length - 2];
+ String endTime = parts[parts.length - 1];
+
+ // Constructing the CCA name by joining the earlier parts
+ StringBuilder nameBuilder = new StringBuilder();
+ for (int i = 0; i < parts.length - 3; i++) {
+ nameBuilder.append(parts[i]);
+ if (i < parts.length - 4) {
+ nameBuilder.append(" "); // Add space between words in the CCA name
+ }
+ }
+ String name = nameBuilder.toString();
+ String timeBlockString = day + " " + startTime + " " + endTime;
+
+ // Check for valid CCA name format
+ if (!isValidCcaName(name)) {
+ throw new IllegalValueException(MESSAGE_CONSTRAINTS);
+ }
+
+ // Further validations can be added (e.g., for valid day, startTime, endTime) if necessary
+
+ return new Cca(name, timeBlockString);
+ }
+
+ /**
+ * Checks if the current TimeBlock object is a CCA.
+ *
+ * @return Always true for Cca objects.
+ */
+ @Override
+ public boolean isCca() {
+ return true;
+ }
+
+ /**
+ * Validates if the given CCA name is in the correct format.
+ *
+ * @param test The CCA name to be tested.
+ * @return True if the name is valid, false otherwise.
+ */
+ public static boolean isValidCcaName(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public String getName() {
+ return this.ccaName;
+ }
+
+ @Override
+ public String getType() {
+ return "CCA";
+ }
+
+ @Override
+ public String toString() {
+ return "Cca: [" + ccaName + "] " + super.toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof Cca)) {
+ return false;
+ }
+
+ Cca otherCca = (Cca) other;
+ return otherCca.getName().equals(getName())
+ && otherCca.getTimeBlockString().equals(getTimeBlockString());
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/timetable/DatedEvent.java b/src/main/java/seedu/address/model/person/timetable/DatedEvent.java
new file mode 100644
index 00000000000..c228b13d0a3
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/timetable/DatedEvent.java
@@ -0,0 +1,201 @@
+package seedu.address.model.person.timetable;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Represents a DatedEvent in the application.
+ * Encapsulates an event that is not recurring on a weekly basis.
+ * Contains details such as the event name, time block, date, and reminder settings.
+ */
+public class DatedEvent extends TimeBlock {
+ public static final String MESSAGE_CONSTRAINTS =
+ "Input should be in the format 'name YYYY-MM-DD HHMM HHMM yes/no', \n"
+ + "where:\n"
+ + "- 'name' represents the name and should not contain spaces.\n"
+ + "- 'YYYY-MM-DD' represents a date (e.g., '2023-10-24').\n"
+ + "- 'HHMM' represents a valid 24-hour time format in half-hour blocks (e.g., 0000, 1230, 2300). \n"
+ + "- The first 'HHMM' represents the starting time (e.g., '0830' for 08:30 AM).\n"
+ + "- The second 'HHMM' represents the ending time (e.g., '1730' for 05:30 PM).\n"
+ + "- y/n represents whether you want a reminder for this event.";
+ public static final String DATE_TIME_VALIDATION_REGEX = "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$";
+ protected static final String DATE_TIME_FORMATTER_PATTERN = "yyyy-MM-dd";
+ private final String name;
+ private final LocalDate date;
+ private final boolean hasReminder;
+
+ /**
+ * Initializes a new DatedEvent with the provided details.
+ *
+ * @param name The name of the event.
+ * @param timeBlockString The time block for the event.
+ * @param dateString The date of the event in the format "YYYY-MM-DD".
+ */
+ public DatedEvent(String name, String timeBlockString, String dateString, boolean reminder) {
+ super(timeBlockString);
+ requireNonNull(name);
+ checkArgument(isValidDateTimeString(dateString), MESSAGE_CONSTRAINTS);
+ this.name = name;
+ this.date = LocalDate.parse(dateString, DateTimeFormatter.ofPattern(DATE_TIME_FORMATTER_PATTERN));
+ this.hasReminder = reminder;
+ }
+
+ /**
+ * Initializes a new DatedEvent with the provided details.
+ *
+ * @param name The name of the event.
+ * @param timeBlockString The time block for the event.
+ * @param date The date of the event.
+ */
+ public DatedEvent(String name, String timeBlockString, LocalDate date, boolean reminder) {
+ super(timeBlockString);
+ requireNonNull(name);
+ this.name = name;
+ this.date = date;
+ this.hasReminder = reminder;
+ }
+
+ /**
+ * Factory method to create a new DatedEvent object from a given unparsed input string.
+ *
+ * The expected format for the input is: "EVENT_NAME DATE YYYY-MM-DD START_TIME END_TIME REMINDER_STATUS"
+ * Where:
+ *
+ *
EVENT_NAME is the name of the event and can contain spaces.
+ *
DATE in 'YYYY-MM-DD' format represents the date of the event.
+ *
START_TIME and END_TIME in 'HHMM' format represent the start and end times of the event respectively.
+ *
REMINDER_STATUS is 'y' if a reminder is set for the event, 'n' otherwise.
+ *
+ *
+ * Example input: "meet Andre 2023-10-10 1030 1130 y"
+ *
+ * @param unparsedInput The input string containing DatedEvent details.
+ * @return A new DatedEvent object.
+ * @throws IllegalArgumentException If the given input does not adhere to the expected format.
+ */
+ public static DatedEvent newDatedEvent(String unparsedInput) { // e.g., "meet Andre 2023-10-10 1030 1130 y"
+ requireNonNull(unparsedInput);
+
+ // Regex to capture the name, date, start time, and end time
+ String regex = "^(.+) (\\d{4}-\\d{2}-\\d{2}) (\\d{4}) (\\d{4}) (y|n)$";
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(unparsedInput);
+
+ checkArgument(matcher.matches(), MESSAGE_CONSTRAINTS);
+
+ // Extracting components using the matcher
+ String nameString = matcher.group(1); // event name
+ String dateString = matcher.group(2); // date
+ String startTime = matcher.group(3); // start time
+ String endTime = matcher.group(4); // end time
+ boolean reminder = matcher.group(5).equals("y"); // reminder status
+
+ checkArgument(isValidDateTimeString(dateString), MESSAGE_CONSTRAINTS);
+
+ // Parse the date
+ LocalDate date = LocalDate.parse(dateString, DateTimeFormatter.ofPattern(DATE_TIME_FORMATTER_PATTERN));
+ String dayOfWeek = date.getDayOfWeek().name();
+
+ // Create the time block
+ String timeBlockString = dayOfWeek + " " + startTime + " " + endTime;
+ checkArgument(isValidTimeBlock(timeBlockString), MESSAGE_CONSTRAINTS);
+
+ return new DatedEvent(nameString, timeBlockString, dateString, reminder);
+ }
+
+ /**
+ * Checks if the provided date time string is valid.
+ *
+ * @param test The date time string to check.
+ * @return true if the string is a valid date time format, false otherwise.
+ */
+ public static boolean isValidDateTimeString(String test) {
+ return test.matches(DATE_TIME_VALIDATION_REGEX);
+ }
+
+ public LocalDate getDate() {
+ return date;
+ }
+
+ @Override
+ public boolean isDatedEvent() {
+ return true;
+ }
+
+ public boolean hasReminder() {
+ return hasReminder;
+ }
+
+ @Override
+ public String toString() {
+ return "DatedEvent: [" + name + "] on " + date.format(DateTimeFormatter.ofPattern(DATE_TIME_FORMATTER_PATTERN))
+ + " " + super.toString() + " Reminder: " + (hasReminder ? "Yes" : "No");
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getType() {
+ return "Event";
+ }
+
+ public String getStringForReminder() {
+ return name + " " + super.getTimeBlockString();
+ }
+
+ /**
+ * Checks if this event's date is before the given date.
+ * @param date The date to compare with.
+ * @return true if this event's date is before the given date.
+ */
+ public boolean isBefore(LocalDate date) {
+ return this.date.isBefore(date);
+ }
+
+ /**
+ * Checks if this event overlaps with another event.
+ * This method assumes that two events overlap if they are on the same date.
+ * @param event The event to compare with.
+ * @return true if the events overlap.
+ */
+ public boolean isOverlap(DatedEvent event) {
+ if (!this.date.equals(event.date)) {
+ return false;
+ }
+ int thisStartTime = Integer.parseInt(this.getStartTime());
+ int thisEndTime = Integer.parseInt(this.getEndTime());
+ int eventStartTime = Integer.parseInt(event.getStartTime());
+ int eventEndTime = Integer.parseInt(event.getEndTime());
+
+ return thisStartTime < eventEndTime && thisEndTime > eventStartTime;
+ }
+
+ @Override
+ public boolean equals(Object e) {
+ if (e == this) {
+ return true;
+ } else if (!(e instanceof DatedEvent)) {
+ return false;
+ } else {
+ DatedEvent other = (DatedEvent) e;
+ return other.getName().equals(getName())
+ && other.getDate().equals(getDate())
+ && other.getTimeBlockString().equals(getTimeBlockString())
+ && other.hasReminder() == hasReminder();
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/timetable/FreeTime.java b/src/main/java/seedu/address/model/person/timetable/FreeTime.java
new file mode 100644
index 00000000000..270627b4fc1
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/timetable/FreeTime.java
@@ -0,0 +1,132 @@
+package seedu.address.model.person.timetable;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.time.DayOfWeek;
+
+/**
+ * Represents a block of free time in a day, with a day of the week and start and end times.
+ * Guarantees: immutable; is valid as declared in {@link #isValidFreeTime(String)}
+ */
+public class FreeTime implements Comparable {
+
+ public static final String MESSAGE_CONSTRAINTS = "Input should be in the format 'DAY HHMM HHMM', \n"
+ + "where 'DAY' is a valid day of the week (e.g., Monday, tuesday, WEDNESDAY), \n"
+ + "and 'HHMM' represents a valid 24-hour time format in half-hour blocks (e.g., 0000, 1230, 2300). \n"
+ + "Day is case-insensitive.";
+
+ public static final String VALIDATION_REGEX = "^(?i)(monday|tuesday|wednesday|thursday|friday|saturday|sunday) "
+ + "([01]?\\d|2[0-3])(00|30) (([01]?\\d|2[0-3])(00|30)|(2400))$";
+
+ public final String freeTimeString;
+ private final DayOfWeek day;
+ private final HalfHourBlocks timeBlocks;
+
+ /**
+ * Constructs a {@code FreeTime}.
+ *
+ * @param freeTime A valid time.
+ */
+ public FreeTime(String freeTime) {
+ requireNonNull(freeTime);
+ checkArgument(isValidFreeTime(freeTime), MESSAGE_CONSTRAINTS);
+
+ String[] parts = freeTime.split(" ");
+ this.day = DayOfWeek.valueOf(parts[0].toUpperCase());
+ int startBlock = Integer.parseInt(parts[1]) / 100 * 2 + (Integer.parseInt(parts[1]) % 100) / 30;
+ int endBlock = Integer.parseInt(parts[2]) / 100 * 2 + (Integer.parseInt(parts[2]) % 100) / 30;
+ this.timeBlocks = new HalfHourBlocks(startBlock, endBlock);
+
+ //Capitalize the first letter of the day
+ String formattedDay = parts[0].substring(0, 1).toUpperCase() + parts[0].substring(1).toLowerCase();
+ this.freeTimeString = formattedDay + " " + parts[1] + " " + parts[2];
+ }
+
+ /**
+ * Returns true if a given string is a valid tag name.
+ */
+ public static boolean isValidFreeTime(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ /**
+ * Compares this FreeTime instance with another instance.
+ * The comparison is primarily based on the day of the week, followed by the start time, and then the end time.
+ *
+ * @param other The other FreeTime instance to compare against.
+ * @return A negative integer, zero, or a positive integer as this FreeTime is less than, equal to, or greater
+ * than the specified FreeTime.
+ */
+ @Override
+ public int compareTo(FreeTime other) {
+ if (this.day.compareTo(other.day) != 0) {
+ return this.day.compareTo(other.day);
+ }
+ // You can further refine this if needed
+ return this.timeBlocks.toString().compareTo(other.timeBlocks.toString());
+ }
+
+ /**
+ * Checks if the current FreeTime overlaps with another FreeTime.
+ * If there's an overlap, returns a new FreeTime representing the overlapping period.
+ *
+ * @param other The other FreeTime to check against.
+ * @return An Optional containing the overlapping FreeTime if it exists, or an empty Optional otherwise.
+ */
+ public FreeTime overlap(FreeTime other) {
+ if (this.day != other.day) {
+ return null;
+ }
+
+ if (this.timeBlocks.overlaps(other.timeBlocks)) {
+ HalfHourBlocks overlapBlocks = this.timeBlocks.getOverlap(other.timeBlocks);
+ // You can convert overlapBlocks back to a string representation if needed
+ return new FreeTime(day + " " + overlapBlocks.toString());
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks if the current {@code FreeTime} overlaps with the specified {@code FreeTime}.
+ *
+ * @param other The other {@code FreeTime} instance to check for overlap.
+ * @return {@code true} if the current {@code FreeTime} overlaps with the specified {@code FreeTime},
+ * {@code false} otherwise.
+ */
+ public boolean isOverlap(FreeTime other) {
+ if (this.day != other.day) {
+ return false;
+ }
+
+ return this.timeBlocks.overlaps(other.timeBlocks);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof FreeTime)) {
+ return false;
+ }
+
+ FreeTime otherFreeTime = (FreeTime) other;
+ return freeTimeString.equals(otherFreeTime.freeTimeString);
+ }
+
+ @Override
+ public int hashCode() {
+ return freeTimeString.hashCode();
+ }
+
+ /**
+ * Format state as text for viewing.
+ */
+ public String toString() {
+ return '[' + freeTimeString + ']';
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/timetable/HalfHourBlocks.java b/src/main/java/seedu/address/model/person/timetable/HalfHourBlocks.java
new file mode 100644
index 00000000000..dcb173f8a30
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/timetable/HalfHourBlocks.java
@@ -0,0 +1,84 @@
+package seedu.address.model.person.timetable;
+
+/**
+ * Represents the half-hour blocks within a day.
+ * Each block is represented by a start time and end time.
+ */
+public class HalfHourBlocks implements Comparable {
+ private final int startHalfHour;
+ private final int endHalfHour;
+
+ /**
+ * Constructs a new HalfHourBlocks with specified start and end half-hour blocks.
+ *
+ * @param startHalfHour The starting half-hour block (inclusive).
+ * @param endHalfHour The ending half-hour block (exclusive).
+ */
+ public HalfHourBlocks(int startHalfHour, int endHalfHour) {
+ this.startHalfHour = startHalfHour;
+ this.endHalfHour = endHalfHour;
+ }
+
+ /**
+ * Checks if the current HalfHourBlocks overlaps with another HalfHourBlocks.
+ *
+ * @param other The other HalfHourBlocks to check against.
+ * @return true if there's an overlap, false otherwise.
+ */
+ public boolean overlaps(HalfHourBlocks other) {
+ return this.startHalfHour < other.endHalfHour && other.startHalfHour < this.endHalfHour;
+ }
+
+ /**
+ * Returns a new HalfHourBlocks representing the overlap between the current and another HalfHourBlocks.
+ *
+ * @param other The other HalfHourBlocks to check against.
+ * @return A new HalfHourBlocks representing the overlap.
+ */
+ public HalfHourBlocks getOverlap(HalfHourBlocks other) {
+ if (!this.overlaps(other)) {
+ return null;
+ }
+ int overlapStart = Math.max(this.startHalfHour, other.startHalfHour);
+ int overlapEnd = Math.min(this.endHalfHour, other.endHalfHour);
+ return new HalfHourBlocks(overlapStart, overlapEnd);
+ }
+
+ @Override
+ public int compareTo(HalfHourBlocks other) {
+ if (this.startHalfHour != other.startHalfHour) {
+ return Integer.compare(this.startHalfHour, other.startHalfHour);
+ } else {
+ return Integer.compare(this.endHalfHour, other.endHalfHour);
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+ HalfHourBlocks that = (HalfHourBlocks) other;
+ return this.startHalfHour == that.startHalfHour && this.endHalfHour == that.endHalfHour;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * startHalfHour + endHalfHour;
+ }
+
+ /**
+ * Returns a string representation of the HalfHourBlocks object.
+ *
+ * @return A string representation of the HalfHourBlocks object.
+ */
+ @Override
+ public String toString() {
+ String startTimeString = String.format("%04d", startHalfHour / 2 * 100 + (startHalfHour % 2) * 30);
+ String endTimeString = String.format("%04d", endHalfHour / 2 * 100 + (endHalfHour % 2) * 30);
+ return startTimeString + " " + endTimeString;
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/timetable/Module.java b/src/main/java/seedu/address/model/person/timetable/Module.java
new file mode 100644
index 00000000000..896e4509980
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/timetable/Module.java
@@ -0,0 +1,133 @@
+package seedu.address.model.person.timetable;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+
+/**
+ * Represents a Module in the application.
+ * Contains information about the module code and its timings.
+ */
+public class Module extends TimeBlock {
+ public static final String MESSAGE_CONSTRAINTS = "Module codes should start with 2-3 alphabetic characters, \n"
+ + "followed by 3-4 digits, and can optionally end with an extra alphabetic character. \n"
+ + "The alphabetic characters are case-insensitive.";
+
+ private static final String VALIDATION_REGEX = "^[a-zA-Z]{2,3}\\d{3,4}[a-zA-Z]?$";
+ private final String moduleName;
+
+ /**
+ * Constructs a {@code Module} with the specified code and time block.
+ *
+ * @param name The module code. Should follow the format described in MESSAGE_CONSTRAINTS.
+ * @param timeBlockString The time block for the module. Expected format: DAY HHMM HHMM.
+ */
+ public Module(String name, String timeBlockString) { //name = name, timeBlockString = DAY HHMM HHMM
+ super(timeBlockString);
+ requireNonNull(name);
+ checkArgument(isValidModuleName(name), MESSAGE_CONSTRAINTS);
+
+ this.moduleName = name;
+ }
+
+ /**
+ * Factory method to create a new Module object from a given unparsed input string.
+ * Expected format for the input string: NAME DAY HHMM HHMM.
+ *
+ * @param unparsedInput The input string containing module details.
+ * @return A new Module object.
+ * @throws IllegalArgumentException If the given input does not adhere to the expected format.
+ */
+ public static Module newModule(String unparsedInput) throws IllegalValueException {
+ requireNonNull(unparsedInput);
+
+ // Split the unparsed input string by whitespace
+ String[] parts = unparsedInput.split("\\s+");
+
+ // Check for valid number of parts
+ if (parts.length != 4) {
+ String errorMsg = "Invalid module input format. Expected: NAME DAY HHMM HHMM" + MESSAGE_CONSTRAINTS;
+ throw new IllegalArgumentException(errorMsg);
+ }
+
+ String name = parts[0];
+ String day = parts[1];
+ String startTime = parts[2];
+ String endTime = parts[3];
+ String timeBlockString = day + " " + startTime + " " + endTime;
+
+ // Check for valid module name format
+ if (!isValidModuleName(name)) {
+ throw new IllegalValueException(MESSAGE_CONSTRAINTS);
+ }
+
+ return new Module(name, timeBlockString);
+ }
+
+ /**
+ * Creates a new Module object with the given name while retaining the timings from the original module.
+ *
+ * @param newName The new name for the module.
+ * @return A new Module object with the updated name.
+ * @throws IllegalArgumentException If the given new name does not adhere to the module naming constraints.
+ */
+ public Module editName(String newName) {
+ requireNonNull(newName);
+ checkArgument(isValidModuleName(newName), MESSAGE_CONSTRAINTS);
+ return new Module(newName, super.getTimeBlockString());
+ }
+
+ @Override
+ public String getName() {
+ return moduleName;
+ }
+
+ @Override
+ public String getType() {
+ return "Module";
+ }
+
+ /**
+ * Checks if the given module name is valid.
+ *
+ * @param test The module name to check.
+ * @return true if the module name is valid, false otherwise.
+ */
+ public static boolean isValidModuleName(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ /**
+ * Checks if the current event is a module.
+ *
+ * @return True since it is a module.
+ */
+ @Override
+ public boolean isModule() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "Module: [" + moduleName + "] " + super.toString();
+ }
+
+ @Override
+ public boolean equals(Object e) {
+ if (e == this) {
+ return true;
+ } else if (!(e instanceof Module)) {
+ return false;
+ } else {
+ Module other = (Module) e;
+ return this.moduleName.equals(other.moduleName)
+ && this.getTimeBlockString().equals(other.getTimeBlockString());
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/timetable/Schedule.java b/src/main/java/seedu/address/model/person/timetable/Schedule.java
new file mode 100644
index 00000000000..9efec242d57
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/timetable/Schedule.java
@@ -0,0 +1,546 @@
+package seedu.address.model.person.timetable;
+
+import static java.util.Objects.isNull;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.logic.commands.exceptions.CommandException;
+
+/**
+ * Represents a schedule consisting of modules, CCAs (Co-Curricular Activities), and dated events.
+ */
+public class Schedule {
+ private final List modulesList = new ArrayList<>();
+ private final List ccasList = new ArrayList<>();
+ private final List datedEventsList = new ArrayList<>();
+
+ public Schedule() {}
+ /**
+ * Creates a new Schedule object.
+ */
+ public Schedule(List modulesList, List ccasList,
+ List datedEventsList) {
+ this.modulesList.addAll(modulesList);
+ this.ccasList.addAll(ccasList);
+ this.datedEventsList.addAll(datedEventsList);
+ }
+ /**
+ * Retrieves the list of time blocks scheduled for the current week.
+ *
+ * @return A list of time blocks for the current week.
+ */
+ public List getThisWeeksSchedule() {
+ LocalDate today = LocalDate.now();
+ LocalDate startOfThisWeek = today
+ .minusDays(today.getDayOfWeek().getValue() - DayOfWeek.MONDAY.getValue());
+ LocalDate endOfThisWeek = startOfThisWeek.plusDays(6);
+
+ List thisWeeksSchedule = new ArrayList<>();
+ thisWeeksSchedule.addAll(modulesList);
+ thisWeeksSchedule.addAll(ccasList);
+ for (DatedEvent event : datedEventsList) {
+ if (event.getDate().isAfter(startOfThisWeek.minusDays(1))
+ && event.getDate().isBefore(endOfThisWeek.plusDays(1))) {
+ thisWeeksSchedule.add(event);
+ }
+ }
+ return thisWeeksSchedule;
+ }
+
+ public List getScheduleForDayOfWeek(int day) {
+ List weekSchedule = getThisWeeksSchedule();
+ List daySchedule = new ArrayList<>();
+ for (TimeBlock timeBlock : weekSchedule) {
+ if (timeBlock.isOnDay(day)) {
+ daySchedule.add(timeBlock);
+ }
+ }
+
+ //sort the list
+ Collections.sort(daySchedule, (tb1, tb2) -> tb1.compareByStartTime(tb2));
+
+ return daySchedule;
+ }
+
+
+ /**
+ * Retrieves the list of dated events.
+ *
+ * @return A list of dated events for the current day.
+ */
+ public List getTodayDatedEvents() {
+ List thisWeeksDatedEvents = new ArrayList<>();
+ for (DatedEvent event : datedEventsList) {
+ if (event.getDate().equals(LocalDate.now()) && event.hasReminder()) {
+ thisWeeksDatedEvents.add(event);
+ }
+ }
+ return thisWeeksDatedEvents;
+ }
+
+ /**
+ * Computes the free time slots for the current week.
+ *
+ * @return A list of free time slots for the current week.
+ */
+ public List getThisWeeksFreeTime() {
+ boolean[][] timeSlots = new boolean[7][48];
+ List freeTimes = new ArrayList<>();
+
+ List thisWeeksScheduleList = getThisWeeksSchedule();
+ // Step 2: Mark occupied slots
+ for (TimeBlock tb : thisWeeksScheduleList) {
+ markSlots(timeSlots, tb);
+ }
+
+ // Step 3: Identify free time slots
+ for (int day = 0; day < 7; day++) {
+ int startSlot = -1;
+ for (int slot = 0; slot < 48; slot++) {
+ if (!timeSlots[day][slot] && startSlot == -1) {
+ // Start of a free time slot
+ startSlot = slot;
+ } else if (timeSlots[day][slot] && startSlot != -1) {
+ // End of a free time slot
+ freeTimes.add(createFreeTime(day, startSlot, slot));
+ startSlot = -1;
+ }
+ }
+ if (startSlot != -1) {
+ freeTimes.add(createFreeTime(day, startSlot, 48)); // The entire day is free.
+ }
+ }
+
+ return freeTimes;
+ }
+
+ /**
+ * Returns true if there is any free time in the schedule for the current week.
+ * A free time is a time slot where there are no scheduled activities.
+ *
+ * @return true if there is any free time in the schedule for the current week, false otherwise.
+ */
+ public boolean hasFreeTime() {
+ List freeTimes = getThisWeeksFreeTime();
+ for (FreeTime freeTime : freeTimes) {
+ if (!isNull(freeTime)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Marks the time slots corresponding to a given time block as occupied.
+ *
+ * @param timeSlots 2D array representing the occupied time slots.
+ * @param tb The time block to be marked.
+ */
+ private void markSlots(boolean[][] timeSlots, TimeBlock tb) {
+ int dayIndex = tb.getDay().getValue() - 1;
+
+ // Convert start and end times to slot indices
+ int startSlot = convertTimeToSlot(tb.getStartTime());
+ int endSlot = convertTimeToSlot(tb.getEndTime());
+
+ // Mark all slots between start and end as occupied
+ for (int slot = startSlot; slot < endSlot; slot++) {
+ timeSlots[dayIndex][slot] = true;
+ }
+ }
+
+ /**
+ * Converts a given time in HHMM format to its corresponding slot index.
+ *
+ * @param time Time in HHMM format.
+ * @return Slot index corresponding to the time.
+ */
+ private int convertTimeToSlot(String time) {
+ int hour = Integer.parseInt(time.substring(0, 2));
+ int minute = Integer.parseInt(time.substring(2));
+ return hour * 2 + minute / 30;
+ }
+
+ /**
+ * Converts a given slot index to its corresponding time in HHMM format.
+ *
+ * @param slot Slot index.
+ * @return Time in HHMM format corresponding to the slot.
+ */
+ private String convertSlotToTime(int slot) {
+ int hour = slot / 2; // each slot represents 30 minutes, so 2 slots represent 1 hour
+ int minute = (slot % 2) * 30; // remainder will be either 0 or 1, representing either 0 or 30 minutes
+
+ // Convert to HHMM format
+ String hourString = (hour < 10) ? "0" + hour : String.valueOf(hour);
+ String minuteString = (minute == 0) ? "00" : "30";
+
+ return hourString + minuteString;
+ }
+
+ /**
+ * Creates a FreeTime object representing a free time slot.
+ *
+ * @param day Day index (0-6).
+ * @param startSlot Start slot index of the free time.
+ * @param endSlot End slot index of the free time.
+ * @return A FreeTime object.
+ */
+ private FreeTime createFreeTime(int day, int startSlot, int endSlot) {
+ // Convert day and slot indices back to a string representation
+ DayOfWeek dayOfWeek = DayOfWeek.of(day + 1);
+ String startTime = convertSlotToTime(startSlot);
+ String endTime = convertSlotToTime(endSlot);
+ return new FreeTime(dayOfWeek.name() + " " + startTime + " " + endTime);
+ }
+
+ /**
+ * Returns an unmodifiable list of dated events in the schedule.
+ *
+ * @return List of dated events.
+ */
+ public List getDatedEventsList() {
+ return Collections.unmodifiableList(datedEventsList);
+ }
+
+
+ /**
+ * Returns an unmodifiable list of modules in the schedule.
+ *
+ * @return List of modules.
+ */
+ public List getModulesList() {
+ return Collections.unmodifiableList(modulesList);
+ }
+
+ /**
+ * Returns an unmodifiable list of CCAs in the schedule.
+ *
+ * @return List of CCAs.
+ */
+ public List getCcasList() {
+ return Collections.unmodifiableList(ccasList);
+ }
+
+ /**
+ * Computes overlapping free time slots with another schedule.
+ *
+ * @param otherSchedule The other schedule to compare with.
+ * @return A list of overlapping free time slots.
+ */
+ public List getThisWeeksFreeTimesWith(Schedule otherSchedule) {
+ List myFreeTimes = getThisWeeksFreeTime();
+ List otherFreeTimes = otherSchedule.getThisWeeksFreeTime();
+
+ List overlappingFreeTimes = new ArrayList<>();
+
+ for (FreeTime myFreeTime : myFreeTimes) {
+ for (FreeTime otherFreeTime : otherFreeTimes) {
+ if (myFreeTime.isOverlap(otherFreeTime)) {
+ overlappingFreeTimes.add(myFreeTime.overlap(otherFreeTime));
+ }
+ }
+ }
+
+ return overlappingFreeTimes;
+ }
+
+ /**
+ * Adds a module to the schedule.
+ *
+ * @param moduleString String representation of the module.
+ */
+ public void addModule(String moduleString) throws IllegalValueException {
+ Module newModule = Module.newModule(moduleString);
+ if (!isOverlapping(newModule)) {
+ modulesList.add(newModule);
+ } else {
+ throw new IllegalArgumentException("Module " + newModule.getName()
+ + " overlaps with " + getOverlappingEvent(newModule) + "!");
+ }
+ }
+
+ /**
+ * Adds a module to the schedule.
+ *
+ * @param module Module to be added.
+ */
+ public void addModule(Module module) {
+ modulesList.add(module);
+ }
+
+ /**
+ * Edits a module in the schedule.
+ *
+ * @param moduleName Name of the module to be edited.
+ * @param timeBlockString String representation of the new module time block.
+ */
+ public void editModule(String moduleName, String timeBlockString) throws IllegalValueException {
+ // Remove all instances of the module with the given name
+ modulesList.removeIf(module -> module.getName().equals(moduleName));
+
+ // Add the new module
+ modulesList.add(Module.newModule(timeBlockString));
+ }
+
+ /**
+ * Removes a module from the schedule.
+ *
+ * @param moduleName Name of the module to be removed.
+ * @throws CommandException If the module to be removed does not exist.
+ */
+ public void deleteModule(String moduleName) throws CommandException {
+ boolean isFound = false;
+ for (Module module : modulesList) {
+ if (module.getName().equals(moduleName)) {
+ modulesList.remove(module);
+ isFound = true;
+ break; // Exit the loop after the first matching module is removed
+ }
+ }
+
+ if (!isFound) {
+ throw new CommandException("Module " + moduleName + " does not exist!\n"
+ + "Please check that you have entered the correct module name!\n");
+ }
+ }
+
+ /**
+ * Adds a CCA to the schedule.
+ *
+ * @param ccaString String representation of the CCA.
+ */
+ public void addCca(String ccaString) throws IllegalValueException {
+ Cca newCca = Cca.newCca(ccaString);
+ if (!isOverlapping(newCca)) {
+ ccasList.add(newCca);
+ } else {
+ throw new IllegalArgumentException("CCA " + newCca.getName() + " overlaps with "
+ + getOverlappingEvent(newCca) + "!");
+ }
+ }
+
+ /**
+ * Adds a CCA in the schedule.
+ *
+ * @param cca CCA to be added.
+ */
+ public void addCca(Cca cca) {
+ ccasList.add(cca);
+ }
+
+ /**
+ * Edits a CCA in the schedule.
+ *
+ * @param ccaName Name of the CCA to be edited.
+ * @param unparsedInput String representation of the new CCA.
+ */
+ public void editCca(String ccaName, String unparsedInput) throws IllegalValueException {
+ // Remove all instances of the CCA with the given name
+ ccasList.removeIf(cca -> cca.getName().equals(ccaName));
+
+ // Add the new CCA
+ ccasList.add(Cca.newCca(unparsedInput));
+ }
+
+ /**
+ * Removes a CCA from the schedule.
+ *
+ * @param ccaName Name of the CCA to be removed.
+ * @throws CommandException If the CCA to be removed does not exist.
+ */
+ public void deleteCca(String ccaName) throws CommandException {
+ boolean isFound = false;
+ for (Cca cca : ccasList) {
+ if (cca.getName().equals(ccaName)) {
+ ccasList.remove(cca);
+ isFound = true;
+ break; // Exit the loop after the first matching CCA is removed
+ }
+ }
+
+ if (!isFound) {
+ throw new CommandException("CCA " + ccaName + " does not exist!"
+ + "Please check that you have entered the correct cca name!\n");
+ }
+ }
+
+ /**
+ * Returns true if the given TimeBlock overlaps with any event in the schedule
+ * @param event the event to be checked
+ * @return true if the given event overlaps with any event in the schedule
+ */
+ public boolean isOverlapping(TimeBlock event) {
+ List totalList = new ArrayList<>();
+ totalList.addAll(modulesList);
+ totalList.addAll(ccasList);
+ for (TimeBlock e : totalList) {
+ if (event.isOverlap(e)) {
+ return true;
+ }
+ }
+ List datedEventsList = getDatedEventsList();
+ LocalDate currentDate = LocalDate.now();
+
+ for (DatedEvent e : datedEventsList) {
+ if (!e.isBefore(currentDate)) {
+ if (event.isOverlap(e)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the given event overlaps with any event in the schedule
+ * @param event the event to be checked
+ * @return true if the given event overlaps with any event in the schedule
+ */
+ public boolean isOverlapping(DatedEvent event) {
+ List totalList = new ArrayList<>();
+ totalList.addAll(modulesList);
+ totalList.addAll(ccasList);
+ for (TimeBlock e : totalList) {
+ if (e.isOverlap(event)) {
+ return true;
+ }
+ }
+ List datedEventsList = getDatedEventsList();
+ LocalDate currentDate = LocalDate.now();
+
+ for (DatedEvent e : datedEventsList) {
+ if (!e.isBefore(currentDate)) {
+ if (event.isOverlap(e)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the event that the given event is overlapping with
+ * @param event the event to be checked
+ * @return the event that the given event is overlapping with
+ */
+ public String getOverlappingEvent(TimeBlock event) {
+ List totalList = new ArrayList<>();
+ totalList.addAll(modulesList);
+ totalList.addAll(ccasList);
+ totalList.addAll(datedEventsList);
+ for (TimeBlock e : totalList) {
+ if (event.isOverlap(e)) {
+ return e.getName();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds a dated event to the schedule.
+ *
+ * @param event Dated event to be added.
+ */
+ public void addDatedEvent(DatedEvent event) {
+ datedEventsList.add(event);
+ }
+
+ /**
+ * Adds a dated event to the schedule.
+ *
+ * @param eventString String representation of the dated event.
+ */
+ public void addDatedEvent(String eventString) {
+ DatedEvent newEvent = DatedEvent.newDatedEvent(eventString);
+ if (!isOverlapping(newEvent)) {
+ datedEventsList.add(newEvent);
+ } else {
+ throw new IllegalArgumentException("Event " + newEvent.getName()
+ + " overlaps with " + getOverlappingEvent(newEvent) + "!");
+ }
+ }
+
+ /**
+ * Edits a dated event in the schedule.
+ *
+ * @param eventName Name of the dated event to be edited.
+ * @param unparsedInput String representation of the new dated event.
+ */
+ public void editDatedEvent(String eventName, String unparsedInput) {
+ // Remove all instances of the dated event with the given name
+ datedEventsList.removeIf(event -> event.getName().equals(eventName));
+
+ // Add the new dated event
+ datedEventsList.add(DatedEvent.newDatedEvent(unparsedInput));
+ }
+
+ /**
+ * Removes a dated event from the schedule.
+ *
+ * @param eventName Name of the dated event to be removed.
+ */
+ public void deleteDatedEvent(String eventName) throws CommandException {
+ boolean isFound = false;
+ for (DatedEvent event : datedEventsList) {
+ if (event.getName().equals(eventName)) {
+ datedEventsList.remove(event);
+ isFound = true;
+ break; // Exit the loop after the first matching event is removed
+ }
+ }
+
+ if (!isFound) {
+ throw new CommandException("Event " + eventName + " does not exist!\n"
+ + "Please check that you have entered the correct event name!\n");
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (!modulesList.isEmpty()) {
+ sb.append("- Modules:\n");
+ for (Module module : modulesList) {
+ sb.append(" ").append(module.toString()).append("\n");
+ }
+ }
+ if (!ccasList.isEmpty()) {
+ sb.append("- CCAs:\n");
+ for (Cca cca : ccasList) {
+ sb.append(" ").append(cca.toString()).append("\n");
+ }
+ }
+ if (!datedEventsList.isEmpty()) {
+ sb.append("- Dated Events:\n");
+ for (DatedEvent event : datedEventsList) {
+ sb.append(" ").append(event.toString()).append("\n");
+ }
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof Schedule)) {
+ return false;
+ }
+ Schedule otherSchedule = (Schedule) other;
+ return modulesList.equals(otherSchedule.modulesList)
+ && ccasList.equals(otherSchedule.ccasList)
+ && datedEventsList.equals(otherSchedule.datedEventsList);
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/timetable/TimeBlock.java b/src/main/java/seedu/address/model/person/timetable/TimeBlock.java
new file mode 100644
index 00000000000..68bec2c1ad1
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/timetable/TimeBlock.java
@@ -0,0 +1,163 @@
+package seedu.address.model.person.timetable;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.time.DayOfWeek;
+
+/**
+ * Represents a block of time in a day, with a day of the week and start and end times.
+ * Guarantees: immutable; is valid as declared in {@link #isValidTimeBlock(String)}}
+ */
+public abstract class TimeBlock implements Comparable {
+
+ public static final String MESSAGE_CONSTRAINTS = "Timing input should be in the format 'DAY HHMM HHMM', \n"
+ + "where 'DAY' is a valid day of the week (e.g., Monday, tuesday, WEDNESDAY), \n"
+ + "and 'HHMM' represents a valid 24-hour time format in half-hour blocks (e.g., 0000, 1230, 2300). \n"
+ + "Day is case-insensitive. The start time must be before the end time.";
+
+ public static final String VALIDATION_REGEX = "^(?i)(monday|tuesday|wednesday|thursday|friday|saturday|sunday) "
+ + "([01]?\\d|2[0-3])(00|30) (([01]?\\d|2[0-3])(00|30)|(2400))$"; //format: (case-insensitive) day 2359 2359
+
+ private String timeBlockString;
+ private final DayOfWeek day;
+ private final HalfHourBlocks timeBlocks;
+
+ /**
+ * Constructs a {@code TimeBlock}.
+ *
+ * @param timeBlockString A valid time.
+ */
+ public TimeBlock(String timeBlockString) {
+ requireNonNull(timeBlockString);
+ checkArgument(isValidTimeBlock(timeBlockString), MESSAGE_CONSTRAINTS);
+
+ String[] parts = timeBlockString.split(" ");
+ this.day = DayOfWeek.valueOf(parts[0].toUpperCase());
+ int startBlock = Integer.parseInt(parts[1]) / 100 * 2 + (Integer.parseInt(parts[1]) % 100) / 30;
+ int endBlock = Integer.parseInt(parts[2]) / 100 * 2 + (Integer.parseInt(parts[2]) % 100) / 30;
+ this.timeBlocks = new HalfHourBlocks(startBlock, endBlock);
+
+ //Capitalize the first letter of the day
+ String formattedDay = parts[0].substring(0, 1).toUpperCase() + parts[0].substring(1).toLowerCase();
+ this.timeBlockString = formattedDay + " " + parts[1] + " " + parts[2];
+ }
+
+ /**
+ * Returns true if a given string is a valid TimeBlock.
+ */
+ public static boolean isValidTimeBlock(String test) {
+ if (!test.matches(VALIDATION_REGEX)) {
+ return false;
+ }
+
+ String[] parts = test.split(" ");
+ int startTime = Integer.parseInt(parts[1]);
+ int endTime = Integer.parseInt(parts[2]);
+ return startTime < endTime;
+ }
+
+ /**
+ * Compares this TimeBlock instance with another instance.
+ * The comparison is primarily based on the day of the week, followed by the start time, and then the end time.
+ *
+ * @param other The other TimeBlock instance to compare against.
+ * @return A negative integer, zero, or a positive integer as this TimeBlock is less than, equal to, or greater
+ * than the specified TimeBlock.
+ */
+ @Override
+ public int compareTo(TimeBlock other) {
+ if (this.day.compareTo(other.day) != 0) {
+ return this.day.compareTo(other.day);
+ }
+ // You can further refine this if needed
+ return this.timeBlocks.toString().compareTo(other.timeBlocks.toString());
+ }
+
+ /**
+ * Compares the start time of this TimeBlock with another TimeBlock's start time.
+ *
+ * @param other The other TimeBlock instance to compare against.
+ * @return A negative integer, zero, or a positive integer as this TimeBlock's start time is less than,
+ * equal to, or greater than the specified TimeBlock's start time.
+ */
+ public int compareByStartTime(TimeBlock other) {
+ return this.timeBlocks.compareTo(other.timeBlocks);
+ }
+
+ /**
+ * Checks if the current {@code TimeBlock} overlaps with the specified {@code TimeBlock}.
+ *
+ * @param other The other {@code TimeBlock} instance to check for overlap.
+ * @return {@code true} if the current {@code TimeBlock} overlaps with the specified {@code TimeBlock},
+ * {@code false} otherwise.
+ */
+ public boolean isOverlap(TimeBlock other) {
+ if (this.day != other.day) {
+ return false;
+ }
+
+ return this.timeBlocks.overlaps(other.timeBlocks);
+ }
+
+ public abstract String getName();
+ public abstract String getType();
+
+ public boolean isModule() {
+ return false;
+ }
+
+ public boolean isCca() {
+ return false;
+ }
+
+ public boolean isFreeTime() {
+ return false;
+ }
+
+ public boolean isDatedEvent() {
+ return false;
+ }
+
+ public String getTimeBlockString() {
+ return timeBlockString;
+ }
+
+ protected DayOfWeek getDay() {
+ return day;
+ }
+
+ /**
+ * Returns the start time of the TimeBlock in 24H format HHMM.
+ * @return A string representing the start time.
+ */
+ public String getStartTime() {
+ String[] parts = timeBlockString.split(" ");
+ return parts[1];
+ }
+
+ /**
+ * Returns the end time of the TimeBlock in 24H format HHMM.
+ * @return A string representing the end time.
+ */
+ public String getEndTime() {
+ String[] parts = timeBlockString.split(" ");
+ return parts[2];
+ }
+
+ /**
+ * Checks if the current TimeBlock is on the specified day.
+ *
+ * @param day The day to check (e.g., 1 for Monday, 2 for Tuesday, etc.)
+ * @return True if the TimeBlock is on the given day, false otherwise.
+ */
+ public boolean isOnDay(int day) {
+ return day == this.day.getValue();
+ }
+
+
+ @Override
+ public String toString() {
+ return '[' + timeBlockString + ']';
+ }
+}
diff --git a/src/main/java/seedu/address/model/user/ReadOnlyUserData.java b/src/main/java/seedu/address/model/user/ReadOnlyUserData.java
new file mode 100644
index 00000000000..db612192aaf
--- /dev/null
+++ b/src/main/java/seedu/address/model/user/ReadOnlyUserData.java
@@ -0,0 +1,23 @@
+package seedu.address.model.user;
+
+import java.util.ArrayList;
+
+import seedu.address.model.person.timetable.DatedEvent;
+
+/**
+ * Unmodifiable view of user data.
+ */
+public interface ReadOnlyUserData {
+
+ /**
+ * Returns the user.
+ * @return
+ */
+ User getUser();
+
+ /**
+ * Returns the dated events.
+ * @return
+ */
+ ArrayList getDatedEvents();
+}
diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/address/model/user/ReadOnlyUserPrefs.java
similarity index 77%
rename from src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
rename to src/main/java/seedu/address/model/user/ReadOnlyUserPrefs.java
index befd58a4c73..7af142f6323 100644
--- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
+++ b/src/main/java/seedu/address/model/user/ReadOnlyUserPrefs.java
@@ -1,4 +1,4 @@
-package seedu.address.model;
+package seedu.address.model.user;
import java.nio.file.Path;
@@ -13,4 +13,6 @@ public interface ReadOnlyUserPrefs {
Path getAddressBookFilePath();
+ Path getUserDataFilePath();
+
}
diff --git a/src/main/java/seedu/address/model/user/User.java b/src/main/java/seedu/address/model/user/User.java
new file mode 100644
index 00000000000..a8caa2ed32b
--- /dev/null
+++ b/src/main/java/seedu/address/model/user/User.java
@@ -0,0 +1,105 @@
+package seedu.address.model.user;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.Birthday;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.person.timetable.DatedEvent;
+import seedu.address.model.person.timetable.Schedule;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Represents a User in the address book.
+ */
+public class User extends Person {
+ private ArrayList datedEvents = new ArrayList<>();
+
+ /**
+ * Every field must be present and not null.
+ */
+ public User(Name name, Phone phone, Email email, Address address, Birthday birthday,
+ Schedule schedule, Set tags, ArrayList datedEvents) {
+ super(name, phone, email, address, birthday, schedule, tags);
+ this.datedEvents = datedEvents;
+ };
+
+ public User() {
+ super();
+ }
+
+ /**
+ * Constructor for User with a person and dated events.
+ */
+ public User(Person user, ArrayList datedEvents) {
+ super(user.getName(), user.getPhone(), user.getEmail(), user.getAddress(), user.getBirthday(),
+ user.getSchedule(), user.getTags());
+ this.datedEvents = datedEvents;
+ }
+
+ /**
+ * Constructor for User with a person.
+ */
+ public User(User user) {
+ this(user, user.getDatedEvents());
+ }
+
+ public ArrayList getDatedEvents() {
+ return datedEvents;
+ }
+
+ public void setDatedEvents(ArrayList datedEvents) {
+ this.datedEvents = datedEvents;
+ }
+
+ public Optional getDatedEvent(String name) {
+ List datedEvents = getSchedule().getDatedEventsList();
+ for (DatedEvent event: datedEvents) {
+ if (event.getName().toLowerCase().equals(name.toLowerCase())) {
+ return Optional.of(event);
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Sets the reminder for the given event.
+ */
+ public void setReminder(DatedEvent event) throws CommandException {
+ getSchedule().deleteDatedEvent(event.getName());
+ getSchedule().addDatedEvent(new DatedEvent(event.getName(), event.getTimeBlockString(),
+ event.getDate().toString(), true));
+ }
+
+ /**
+ * Removes the reminder for the given event.
+ */
+ public void removeReminder(DatedEvent event) throws CommandException {
+ getSchedule().deleteDatedEvent(event.getName());
+ getSchedule().addDatedEvent(new DatedEvent(event.getName(), event.getTimeBlockString(),
+ event.getDate().toString(), false));
+ }
+
+ /**
+ * Returns a string of the events that are happening today and have reminders.
+ */
+ public String returnRemindedEvent() {
+ StringBuilder sb = new StringBuilder();
+ List datedEvents = getSchedule().getTodayDatedEvents();
+ for (DatedEvent event: datedEvents) {
+ if (event.hasReminder() && event.getDate().equals(LocalDate.now())) {
+ sb.append(event.getStringForReminder() + "\n");
+ }
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/user/UserData.java b/src/main/java/seedu/address/model/user/UserData.java
new file mode 100644
index 00000000000..8528a36b82b
--- /dev/null
+++ b/src/main/java/seedu/address/model/user/UserData.java
@@ -0,0 +1,99 @@
+package seedu.address.model.user;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.ArrayList;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.timetable.DatedEvent;
+
+/**
+ * Represents a User in the address book.
+ */
+public class UserData implements ReadOnlyUserData {
+
+ private User user = new User();
+ private final ObservableList internalList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableList =
+ FXCollections.unmodifiableObservableList(internalList);
+
+
+ public UserData() {};
+
+ public UserData(User user) {
+ this.user = user;
+ }
+
+ public UserData(ReadOnlyUserData userData) {
+ this(userData.getUser());
+ }
+
+ public void setDatedEvents(ArrayList datedEvents) {
+ this.user.setDatedEvents(datedEvents);
+ }
+
+ /**
+ * Replaces the contents of the user with {@code neData}.
+ * {@code newData} must not be null.
+ */
+ public void resetData(ReadOnlyUserData newData) {
+ requireNonNull(newData);
+ this.setUser(newData.getUser());
+ this.setDatedEvents(newData.getDatedEvents());
+ }
+
+ public boolean sameAsUserPhone(Person person) {
+ return user.isSamePhone(person);
+ }
+
+ public boolean sameAsUserEmail(Person person) {
+ return user.isSameEmail(person);
+ }
+
+ public boolean sameAsUser(Person person) {
+ return user.isSamePerson(person);
+ }
+
+ public ObservableList getUserView() {
+ return internalUnmodifiableList;
+ }
+
+ public void setUser(User user) {
+ this.user = user;
+ }
+
+ @Override
+ public User getUser() {
+ return user;
+ }
+
+ @Override
+ public ArrayList getDatedEvents() {
+ return user.getDatedEvents();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof UserData)) {
+ return false;
+ }
+
+ UserData otherUserData = (UserData) other;
+ return user.equals(otherUserData.user);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("User : " + user.toString());
+ return sb.toString();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/user/UserPrefs.java
similarity index 84%
rename from src/main/java/seedu/address/model/UserPrefs.java
rename to src/main/java/seedu/address/model/user/UserPrefs.java
index 6be655fb4c7..dc3d48c9dd0 100644
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ b/src/main/java/seedu/address/model/user/UserPrefs.java
@@ -1,4 +1,4 @@
-package seedu.address.model;
+package seedu.address.model.user;
import static java.util.Objects.requireNonNull;
@@ -15,6 +15,7 @@ public class UserPrefs implements ReadOnlyUserPrefs {
private GuiSettings guiSettings = new GuiSettings();
private Path addressBookFilePath = Paths.get("data" , "addressbook.json");
+ private Path userDataFilePath = Paths.get("data" , "userdata.json");
/**
* Creates a {@code UserPrefs} with default values.
@@ -36,6 +37,7 @@ public void resetData(ReadOnlyUserPrefs newUserPrefs) {
requireNonNull(newUserPrefs);
setGuiSettings(newUserPrefs.getGuiSettings());
setAddressBookFilePath(newUserPrefs.getAddressBookFilePath());
+ setUserDataFilePath(newUserPrefs.getUserDataFilePath());
}
public GuiSettings getGuiSettings() {
@@ -51,11 +53,20 @@ public Path getAddressBookFilePath() {
return addressBookFilePath;
}
+ public Path getUserDataFilePath() {
+ return userDataFilePath;
+ }
+
public void setAddressBookFilePath(Path addressBookFilePath) {
requireNonNull(addressBookFilePath);
this.addressBookFilePath = addressBookFilePath;
}
+ public void setUserDataFilePath(Path userDataFilePath) {
+ requireNonNull(userDataFilePath);
+ this.userDataFilePath = userDataFilePath;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
index 1806da4facf..3515cfe283f 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -1,5 +1,6 @@
package seedu.address.model.util;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
@@ -7,11 +8,17 @@
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Birthday;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.timetable.DatedEvent;
+import seedu.address.model.person.timetable.Schedule;
import seedu.address.model.tag.Tag;
+import seedu.address.model.user.ReadOnlyUserData;
+import seedu.address.model.user.User;
+import seedu.address.model.user.UserData;
/**
* Contains utility methods for populating {@code AddressBook} with sample data.
@@ -20,26 +27,43 @@ 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"),
- getTagSet("friends")),
+ new Address("Blk 30 Geylang Street 29, #06-40"), new Birthday("2001-11-01"),
+ new Schedule(),
+ getTagSet("friends")),
new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"),
- new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"),
+ new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), new Birthday("2001-11-01"),
+ new Schedule(),
getTagSet("colleagues", "friends")),
new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"),
- new Address("Blk 11 Ang Mo Kio Street 74, #11-04"),
+ new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), new Birthday("2000-01-01"),
+ new Schedule(),
getTagSet("neighbours")),
new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"),
- new Address("Blk 436 Serangoon Gardens Street 26, #16-43"),
+ new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), new Birthday("2000-01-01"),
+ new Schedule(),
getTagSet("family")),
new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"),
- new Address("Blk 47 Tampines Street 20, #17-35"),
+ new Address("Blk 47 Tampines Street 20, #17-35"), new Birthday("2000-01-01"),
+ new Schedule(),
getTagSet("classmates")),
new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"),
- new Address("Blk 45 Aljunied Street 85, #11-31"),
+ new Address("Blk 45 Aljunied Street 85, #11-31"), new Birthday("2000-01-01"),
+ new Schedule(),
getTagSet("colleagues"))
};
}
+ public static User getSampleUser() {
+ return new User(new Name("Me"), new Phone("00000000"), new Email("me@example.com"),
+ new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), new Birthday("2000-01-01"),
+ new Schedule(),
+ getTagSet("me"), new ArrayList());
+ }
+
+ public static ReadOnlyUserData getSampleUserData() {
+ return new UserData(getSampleUser());
+ }
+
public static ReadOnlyAddressBook getSampleAddressBook() {
AddressBook sampleAb = new AddressBook();
for (Person samplePerson : getSamplePersons()) {
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
index bd1ca0f56c8..c630591b87d 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
@@ -11,16 +11,20 @@
import seedu.address.commons.exceptions.IllegalValueException;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Birthday;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.timetable.Schedule;
import seedu.address.model.tag.Tag;
+import seedu.address.storage.timetable.JsonAdaptedSchedule;
+
/**
* Jackson-friendly version of {@link Person}.
*/
-class JsonAdaptedPerson {
+public class JsonAdaptedPerson {
public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!";
@@ -28,7 +32,9 @@ class JsonAdaptedPerson {
private final String phone;
private final String email;
private final String address;
+ private final String birthday;
private final List tags = new ArrayList<>();
+ private final JsonAdaptedSchedule schedule;
/**
* Constructs a {@code JsonAdaptedPerson} with the given person details.
@@ -36,11 +42,15 @@ class JsonAdaptedPerson {
@JsonCreator
public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone,
@JsonProperty("email") String email, @JsonProperty("address") String address,
+ @JsonProperty("birthday") String birthday,
+ @JsonProperty("schedule") JsonAdaptedSchedule schedule,
@JsonProperty("tags") List tags) {
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
+ this.birthday = birthday;
+ this.schedule = schedule;
if (tags != null) {
this.tags.addAll(tags);
}
@@ -54,6 +64,8 @@ public JsonAdaptedPerson(Person source) {
phone = source.getPhone().value;
email = source.getEmail().value;
address = source.getAddress().value;
+ birthday = source.getBirthday().toString();
+ schedule = new JsonAdaptedSchedule(source.getSchedule());
tags.addAll(source.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList()));
@@ -102,8 +114,20 @@ public Person toModelType() throws IllegalValueException {
}
final Address modelAddress = new Address(address);
+ if (birthday == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Birthday"));
+ }
+ if (!Birthday.isValidBirthday(birthday)) {
+ throw new IllegalValueException(Birthday.MESSAGE_CONSTRAINTS);
+ }
+ final Birthday modelBirthday = new Birthday(birthday);
+ if (schedule == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Schedule"));
+ }
+ final Schedule modelSchedule = schedule.toModelType();
final Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
+
+ return new Person(modelName, modelPhone, modelEmail, modelAddress, modelBirthday, modelSchedule, modelTags);
}
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java
index 0df22bdb754..ac46d16524f 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedTag.java
@@ -9,7 +9,7 @@
/**
* Jackson-friendly version of {@link Tag}.
*/
-class JsonAdaptedTag {
+public class JsonAdaptedTag {
private final String tagName;
@@ -36,6 +36,7 @@ public String getTagName() {
/**
* Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object.
*
+ * @return Tag object.
* @throws IllegalValueException if there were any data constraints violated in the adapted tag.
*/
public Tag toModelType() throws IllegalValueException {
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedUser.java b/src/main/java/seedu/address/storage/JsonAdaptedUser.java
new file mode 100644
index 00000000000..dd1f5e0ad74
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedUser.java
@@ -0,0 +1,162 @@
+package seedu.address.storage;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.Birthday;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Phone;
+import seedu.address.model.person.timetable.DatedEvent;
+import seedu.address.model.person.timetable.Schedule;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.user.User;
+import seedu.address.storage.timetable.JsonAdaptedDatedEvent;
+import seedu.address.storage.timetable.JsonAdaptedSchedule;
+
+
+/**
+ * Jackson-friendly version of {@link User}.
+ */
+public class JsonAdaptedUser {
+
+ public static final String MISSING_FIELD_MESSAGE_FORMAT = "User's %s field is missing!";
+
+ private final String name;
+ private final String phone;
+ private final String email;
+ private final String address;
+ private final String birthday;
+ private final JsonAdaptedSchedule schedule;
+ private final List tags = new ArrayList<>();
+ private final List datedEvents = new ArrayList<>();
+
+ /**
+ * Constructs a {@code JsonAdaptedUser} with the given person details.
+ */
+ @JsonCreator
+ public JsonAdaptedUser(@JsonProperty("name") String name, @JsonProperty("phone") String phone,
+ @JsonProperty("email") String email, @JsonProperty("address") String address,
+ @JsonProperty("birthday") String birthday,
+ @JsonProperty("tags") List tags,
+ @JsonProperty("schedule") JsonAdaptedSchedule schedule,
+ @JsonProperty("datedEvents") List datedEvents) {
+ this.name = name;
+ this.phone = phone;
+ this.email = email;
+ this.address = address;
+ this.birthday = birthday;
+ this.schedule = schedule;
+ if (tags != null) {
+ this.tags.addAll(tags);
+ }
+
+ if (datedEvents != null) {
+ this.datedEvents.addAll(datedEvents);
+ }
+ }
+
+ /**
+ * Converts a given {@code User} into this class for Jackson use.
+ */
+ public JsonAdaptedUser(User source) {
+ name = source.getName().fullName;
+ phone = source.getPhone().value;
+ email = source.getEmail().value;
+ address = source.getAddress().value;
+ birthday = source.getBirthday().toString();
+ schedule = new JsonAdaptedSchedule(source.getSchedule());
+ tags.addAll(source.getTags().stream()
+ .map(JsonAdaptedTag::new)
+ .collect(Collectors.toList()));
+ datedEvents.addAll(source.getDatedEvents().stream()
+ .map(JsonAdaptedDatedEvent::new)
+ .collect(Collectors.toList()));
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted person object into the model's {@code User} object.
+ *
+ * @return User object.
+ * @throws IllegalValueException if there were any data constraints violated in the adapted person.
+ */
+ public User toModelType() throws IllegalValueException {
+ final List personTags = new ArrayList<>();
+ for (JsonAdaptedTag tag : tags) {
+ personTags.add(tag.toModelType());
+ }
+
+ final List personDatedEvents = new ArrayList<>();
+ for (JsonAdaptedDatedEvent datedEvent : datedEvents) {
+ personDatedEvents.add(datedEvent.toModelType());
+ }
+
+ if (name == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()));
+ }
+ if (!Name.isValidName(name)) {
+ throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS);
+ }
+
+ final Name modelName = new Name(name);
+
+ if (phone == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()));
+ }
+ if (!Phone.isValidPhone(phone)) {
+ throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS);
+ }
+
+ final Phone modelPhone = new Phone(phone);
+
+ if (email == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()));
+ }
+ if (!Email.isValidEmail(email)) {
+ throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS);
+ }
+
+ final Email modelEmail = new Email(email);
+
+ if (address == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()));
+ }
+ if (!Address.isValidAddress(address)) {
+ throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS);
+ }
+
+ final Address modelAddress = new Address(address);
+
+ if (birthday == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT,
+ Birthday.class.getSimpleName()));
+ }
+ if (!Birthday.isValidBirthday(birthday)) {
+ throw new IllegalValueException(Birthday.MESSAGE_CONSTRAINTS);
+ }
+
+ final Birthday modelBirthday = new Birthday(birthday);
+
+ if (schedule == null) {
+ throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "Schedule"));
+ }
+
+ final Schedule modelSchedule = schedule.toModelType();
+
+ final Set modelTags = new HashSet<>(personTags);
+ final ArrayList modelDatedEvents = new ArrayList<>(personDatedEvents);
+
+ return new User(modelName, modelPhone, modelEmail, modelAddress, modelBirthday, modelSchedule,
+ modelTags, modelDatedEvents);
+ }
+
+}
+
diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
index 41e06f264e1..951eef0f468 100644
--- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
+++ b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java
@@ -40,6 +40,7 @@ public Optional readAddressBook() throws DataLoadingExcepti
* Similar to {@link #readAddressBook()}.
*
* @param filePath location of the data. Cannot be null.
+ * @return AddressBook object wrapped with an Optional.
* @throws DataLoadingException if loading the data from storage failed.
*/
public Optional readAddressBook(Path filePath) throws DataLoadingException {
diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
index 5efd834091d..28b7a448b75 100644
--- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
+++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
@@ -17,7 +17,7 @@
* An Immutable AddressBook that is serializable to JSON format.
*/
@JsonRootName(value = "addressbook")
-class JsonSerializableAddressBook {
+public class JsonSerializableAddressBook {
public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s).";
@@ -43,6 +43,7 @@ public JsonSerializableAddressBook(ReadOnlyAddressBook source) {
/**
* Converts this address book into the model's {@code AddressBook} object.
*
+ * @return AddressBook object.
* @throws IllegalValueException if there were any data constraints violated.
*/
public AddressBook toModelType() throws IllegalValueException {
diff --git a/src/main/java/seedu/address/storage/JsonSerializableUserData.java b/src/main/java/seedu/address/storage/JsonSerializableUserData.java
new file mode 100644
index 00000000000..1c607a2fd32
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonSerializableUserData.java
@@ -0,0 +1,46 @@
+package seedu.address.storage;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonRootName;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.user.ReadOnlyUserData;
+import seedu.address.model.user.UserData;
+
+/**
+ * An Immutable UserData that is serializable to JSON format.
+ */
+@JsonRootName(value = "userdata")
+public class JsonSerializableUserData {
+ private final JsonAdaptedUser user;
+
+ /**
+ * Constructs a {@code JsonSerializableUserData} with the given persons.
+ */
+ @JsonCreator
+ public JsonSerializableUserData(@JsonProperty("user") JsonAdaptedUser user) {
+ this.user = user;
+ }
+
+ /**
+ * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use.
+ *
+ * @param source future changes to this will not affect the created {@code JsonSerializableUserData}.
+ */
+ public JsonSerializableUserData(ReadOnlyUserData source) {
+ user = new JsonAdaptedUser(source.getUser());
+ }
+
+ /**
+ * Converts this address book into the model's {@code UserData} object.
+ *
+ * @return UserData object.
+ * @throws IllegalValueException if there were any data constraints violated.
+ */
+ public UserData toModelType() throws IllegalValueException {
+ UserData userData = new UserData(this.user.toModelType());
+ return userData;
+ }
+
+}
diff --git a/src/main/java/seedu/address/storage/JsonUserDataStorage.java b/src/main/java/seedu/address/storage/JsonUserDataStorage.java
new file mode 100644
index 00000000000..820d21417c3
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonUserDataStorage.java
@@ -0,0 +1,81 @@
+package seedu.address.storage;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.exceptions.DataLoadingException;
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.commons.util.FileUtil;
+import seedu.address.commons.util.JsonUtil;
+import seedu.address.model.user.ReadOnlyUserData;
+
+/**
+ * A class to access AddressBook data stored as a json file on the hard disk.
+ */
+public class JsonUserDataStorage implements UserDataStorage {
+
+ private static final Logger logger = LogsCenter.getLogger(JsonUserDataStorage.class);
+
+ private Path filePath;
+
+ public JsonUserDataStorage(Path filePath) {
+ this.filePath = filePath;
+ }
+
+ public Path getUserDataFilePath() {
+ return filePath;
+ }
+
+ @Override
+ public Optional readUserData() throws DataLoadingException {
+ return readUserData(filePath);
+ }
+
+ /**
+ * Similar to {@link #readUserData()}.
+ *
+ * @param filePath location of the data. Cannot be null.
+ * @throws DataLoadingException if loading the data from storage failed.
+ */
+ public Optional readUserData(Path filePath) throws DataLoadingException {
+ requireNonNull(filePath);
+
+ Optional jsonUserData = JsonUtil.readJsonFile(
+ filePath, JsonSerializableUserData.class);
+ if (!jsonUserData.isPresent()) {
+ return Optional.empty();
+ }
+
+ try {
+ return Optional.of(jsonUserData.get().toModelType());
+ } catch (IllegalValueException ive) {
+ logger.info("Illegal values found in " + filePath + ": " + ive.getMessage());
+ throw new DataLoadingException(ive);
+ }
+ }
+
+ @Override
+ public void saveUserData(ReadOnlyUserData userData) throws IOException {
+ saveUserData(userData, filePath);
+ }
+
+ /**
+ * Similar to {@link #saveUserData(ReadOnlyUserData)}.
+ *
+ * @param filePath location of the data. Cannot be null.
+ */
+ public void saveUserData(ReadOnlyUserData userData, Path filePath) throws IOException {
+ requireNonNull(userData);
+ requireNonNull(filePath);
+
+ FileUtil.createIfMissing(filePath);
+ JsonUtil.saveJsonFile(new JsonSerializableUserData(userData), filePath);
+ }
+
+}
+
diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java
index 48a9754807d..febbc23ec17 100644
--- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java
+++ b/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java
@@ -6,8 +6,8 @@
import seedu.address.commons.exceptions.DataLoadingException;
import seedu.address.commons.util.JsonUtil;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
+import seedu.address.model.user.ReadOnlyUserPrefs;
+import seedu.address.model.user.UserPrefs;
/**
* A class to access UserPrefs stored in the hard disk as a json file
diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java
index 9fba0c7a1d6..7e0ccb07745 100644
--- a/src/main/java/seedu/address/storage/Storage.java
+++ b/src/main/java/seedu/address/storage/Storage.java
@@ -6,13 +6,14 @@
import seedu.address.commons.exceptions.DataLoadingException;
import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
+import seedu.address.model.user.ReadOnlyUserData;
+import seedu.address.model.user.ReadOnlyUserPrefs;
+import seedu.address.model.user.UserPrefs;
/**
* API of the Storage component
*/
-public interface Storage extends AddressBookStorage, UserPrefsStorage {
+public interface Storage extends AddressBookStorage, UserPrefsStorage, UserDataStorage {
@Override
Optional readUserPrefs() throws DataLoadingException;
@@ -29,4 +30,10 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage {
@Override
void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException;
+ @Override
+ Optional readUserData() throws DataLoadingException;
+
+ @Override
+ void saveUserData(ReadOnlyUserData userData) throws IOException;
+
}
diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java
index 8b84a9024d5..37d0731a27b 100644
--- a/src/main/java/seedu/address/storage/StorageManager.java
+++ b/src/main/java/seedu/address/storage/StorageManager.java
@@ -8,8 +8,9 @@
import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.exceptions.DataLoadingException;
import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
+import seedu.address.model.user.ReadOnlyUserData;
+import seedu.address.model.user.ReadOnlyUserPrefs;
+import seedu.address.model.user.UserPrefs;
/**
* Manages storage of AddressBook data in local storage.
@@ -19,13 +20,16 @@ public class StorageManager implements Storage {
private static final Logger logger = LogsCenter.getLogger(StorageManager.class);
private AddressBookStorage addressBookStorage;
private UserPrefsStorage userPrefsStorage;
+ private UserDataStorage userDataStorage;
/**
* Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}.
*/
- public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) {
+ public StorageManager(AddressBookStorage addressBookStorage,
+ UserPrefsStorage userPrefsStorage, UserDataStorage userDataStorage) {
this.addressBookStorage = addressBookStorage;
this.userPrefsStorage = userPrefsStorage;
+ this.userDataStorage = userDataStorage;
}
// ================ UserPrefs methods ==============================
@@ -75,4 +79,33 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) thro
addressBookStorage.saveAddressBook(addressBook, filePath);
}
+ // ================ UserData methods ==============================
+
+ @Override
+ public Path getUserDataFilePath() {
+ return userDataStorage.getUserDataFilePath();
+ }
+
+ @Override
+ public Optional readUserData() throws DataLoadingException {
+ return readUserData(userDataStorage.getUserDataFilePath());
+ }
+
+ @Override
+ public Optional readUserData(Path filePath) throws DataLoadingException {
+ logger.fine("Attempting to read data from file: " + filePath);
+ return userDataStorage.readUserData(filePath);
+ }
+
+ @Override
+ public void saveUserData(ReadOnlyUserData userData) throws IOException {
+ saveUserData(userData, userDataStorage.getUserDataFilePath());
+ }
+
+ @Override
+ public void saveUserData(ReadOnlyUserData userData, Path filePath) throws IOException {
+ logger.fine("Attempting to write to data file: " + filePath);
+ userDataStorage.saveUserData(userData, filePath);
+ }
+
}
diff --git a/src/main/java/seedu/address/storage/UserDataStorage.java b/src/main/java/seedu/address/storage/UserDataStorage.java
new file mode 100644
index 00000000000..549d9f1b9cf
--- /dev/null
+++ b/src/main/java/seedu/address/storage/UserDataStorage.java
@@ -0,0 +1,46 @@
+package seedu.address.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+
+import seedu.address.commons.exceptions.DataLoadingException;
+import seedu.address.model.user.ReadOnlyUserData;
+
+/**
+ * Represents a storage for {@link seedu.address.model.AddressBook}.
+ */
+public interface UserDataStorage {
+
+ /**
+ * Returns the file path of the data file.
+ */
+ Path getUserDataFilePath();
+
+ /**
+ * Returns UserData data as a {@link ReadOnlyUserData}.
+ * Returns {@code Optional.empty()} if storage file is not found.
+ *
+ * @throws DataLoadingException if loading the data from storage failed.
+ */
+ Optional readUserData() throws DataLoadingException;
+
+ /**
+ * @see #getUserDataFilePath()
+ */
+ Optional readUserData(Path filePath) throws DataLoadingException;
+
+ /**
+ * Saves the given {@link ReadOnlyUserData \} to the storage.
+ * @param userData cannot be null.
+ * @throws IOException if there was any problem writing to the file.
+ */
+ void saveUserData(ReadOnlyUserData userData) throws IOException;
+
+ /**
+ * @see #saveUserData(ReadOnlyUserData)
+ */
+ void saveUserData(ReadOnlyUserData userData, Path filePath) throws IOException;
+
+}
+
diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/address/storage/UserPrefsStorage.java
index e94ca422ea8..4fe47d58999 100644
--- a/src/main/java/seedu/address/storage/UserPrefsStorage.java
+++ b/src/main/java/seedu/address/storage/UserPrefsStorage.java
@@ -5,11 +5,11 @@
import java.util.Optional;
import seedu.address.commons.exceptions.DataLoadingException;
-import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.UserPrefs;
+import seedu.address.model.user.ReadOnlyUserPrefs;
+import seedu.address.model.user.UserPrefs;
/**
- * Represents a storage for {@link seedu.address.model.UserPrefs}.
+ * Represents a storage for {@link seedu.address.model.user.UserPrefs}.
*/
public interface UserPrefsStorage {
@@ -27,7 +27,7 @@ public interface UserPrefsStorage {
Optional readUserPrefs() throws DataLoadingException;
/**
- * Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage.
+ * Saves the given {@link seedu.address.model.user.ReadOnlyUserPrefs} to the storage.
* @param userPrefs cannot be null.
* @throws IOException if there was any problem writing to the file.
*/
diff --git a/src/main/java/seedu/address/storage/timetable/JsonAdaptedCca.java b/src/main/java/seedu/address/storage/timetable/JsonAdaptedCca.java
new file mode 100644
index 00000000000..1a5f1a24f71
--- /dev/null
+++ b/src/main/java/seedu/address/storage/timetable/JsonAdaptedCca.java
@@ -0,0 +1,50 @@
+package seedu.address.storage.timetable;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.timetable.Cca;
+
+/**
+ * Jackson-friendly version of {@link Cca}.
+ */
+public class JsonAdaptedCca {
+
+ private final String name;
+ private final String timeBlockString;
+
+ /**
+ * Constructs a {@code JsonAdaptedCca} with the given {@code CcaName}.
+ */
+ @JsonCreator
+ public JsonAdaptedCca(@JsonProperty("name") String name,
+ @JsonProperty("timeblock") String timeBlockString) {
+ this.name = name;
+ this.timeBlockString = timeBlockString;
+ }
+
+ /**
+ * Converts a given {@code Cca} into this class for Jackson use.
+ */
+ public JsonAdaptedCca(Cca source) {
+ name = source.getName();
+ timeBlockString = source.getTimeBlockString();
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted Cca object into the model's {@code Cca} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted cca.
+ */
+ public Cca toModelType() throws IllegalValueException {
+ if (name == null) {
+ throw new IllegalValueException("CCA name should not be null");
+ }
+ if (timeBlockString == null) {
+ throw new IllegalValueException("Time block should not be null");
+ }
+
+ return new Cca(name, timeBlockString);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/timetable/JsonAdaptedDatedEvent.java b/src/main/java/seedu/address/storage/timetable/JsonAdaptedDatedEvent.java
new file mode 100644
index 00000000000..eef83cb0e4c
--- /dev/null
+++ b/src/main/java/seedu/address/storage/timetable/JsonAdaptedDatedEvent.java
@@ -0,0 +1,67 @@
+package seedu.address.storage.timetable;
+
+import java.time.LocalDate;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.timetable.DatedEvent;
+import seedu.address.model.person.timetable.TimeBlock;
+
+/**
+ * Jackson-friendly version of {@link DatedEvent}.
+ */
+public class JsonAdaptedDatedEvent {
+
+ private final String name;
+ private final String timeBlockString;
+ private final String localDateString;
+ private final boolean reminder;
+
+ /**
+ * Constructs a {@code JsonAdaptedDatedEvent} with the given {@code DatedEventName}.
+ */
+ @JsonCreator
+ public JsonAdaptedDatedEvent(@JsonProperty("name") String name,
+ @JsonProperty("timeblock") String timeBlockString,
+ @JsonProperty("date") String localDateString,
+ @JsonProperty("reminder") boolean reminder) {
+ this.name = name;
+ this.timeBlockString = timeBlockString;
+ this.localDateString = localDateString;
+ this.reminder = reminder;
+ }
+
+ /**
+ * Converts a given {@code DatedEvent} into this class for Jackson use.
+ */
+ public JsonAdaptedDatedEvent(DatedEvent source) {
+ name = source.getName();
+ timeBlockString = source.getTimeBlockString();
+ LocalDate date = source.getDate();
+ localDateString = date.toString();
+ reminder = source.hasReminder();
+ }
+
+
+ /**
+ * Converts this Jackson-friendly adapted DatedEvent object into the model's {@code DatedEvent} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted datedEvent.
+ */
+ public DatedEvent toModelType() throws IllegalValueException {
+ if (!DatedEvent.isValidDateTimeString(localDateString)) {
+ throw new IllegalValueException(DatedEvent.MESSAGE_CONSTRAINTS);
+ }
+
+ if (!TimeBlock.isValidTimeBlock(timeBlockString)) {
+ throw new IllegalValueException(TimeBlock.MESSAGE_CONSTRAINTS);
+ }
+
+ LocalDate date = LocalDate.parse(localDateString);
+ return new DatedEvent(name, timeBlockString, date, reminder);
+ }
+
+}
+
diff --git a/src/main/java/seedu/address/storage/timetable/JsonAdaptedModule.java b/src/main/java/seedu/address/storage/timetable/JsonAdaptedModule.java
new file mode 100644
index 00000000000..283f29c6f87
--- /dev/null
+++ b/src/main/java/seedu/address/storage/timetable/JsonAdaptedModule.java
@@ -0,0 +1,50 @@
+package seedu.address.storage.timetable;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.timetable.Module;
+
+/**
+ * Jackson-friendly version of {@link Module}.
+ */
+public class JsonAdaptedModule {
+
+ private final String name;
+ private final String timeBlockString;
+
+ /**
+ * Constructs a {@code JsonAdaptedModule} with the given {@code ModuleName}.
+ */
+ @JsonCreator
+ public JsonAdaptedModule(@JsonProperty("name") String name,
+ @JsonProperty("timeblock") String timeBlockString) {
+ this.name = name;
+ this.timeBlockString = timeBlockString;
+ }
+
+ /**
+ * Converts a given {@code Module} into this class for Jackson use.
+ */
+ public JsonAdaptedModule(Module source) {
+ name = source.getName();
+ timeBlockString = source.getTimeBlockString();
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted Module object into the model's {@code Module} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted module.
+ */
+ public Module toModelType() throws IllegalValueException {
+ if (name == null) {
+ throw new IllegalValueException("Module name should not be null");
+ }
+ if (timeBlockString == null) {
+ throw new IllegalValueException("Time block should not be null");
+ }
+
+ return new Module(name, timeBlockString);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/timetable/JsonAdaptedSchedule.java b/src/main/java/seedu/address/storage/timetable/JsonAdaptedSchedule.java
new file mode 100644
index 00000000000..f28188f6be9
--- /dev/null
+++ b/src/main/java/seedu/address/storage/timetable/JsonAdaptedSchedule.java
@@ -0,0 +1,107 @@
+package seedu.address.storage.timetable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.timetable.Cca;
+import seedu.address.model.person.timetable.DatedEvent;
+import seedu.address.model.person.timetable.Module;
+import seedu.address.model.person.timetable.Schedule;
+
+/**
+ * Jackson-friendly version of {@link Schedule}.
+ */
+public class JsonAdaptedSchedule {
+ private List datedEvents = new ArrayList<>();
+ private List modules = new ArrayList<>();
+ private List ccas = new ArrayList<>();
+
+ /**
+ * Constructs a {@code JsonAdaptedSchedule} with the given {@code DatedEvent}.
+ */
+ @JsonCreator
+ public JsonAdaptedSchedule(@JsonProperty("datedEvents") List datedEvents,
+ @JsonProperty("modules") List modules,
+ @JsonProperty("cca") List cca) {
+ this.datedEvents = datedEvents;
+ this.modules = modules;
+ this.ccas = cca;
+ }
+
+ /**
+ * Converts a given {@code Schedule} into this class for Jackson use.
+ */
+ public JsonAdaptedSchedule(Schedule source) {
+ List datedEventList = source.getDatedEventsList();
+ List modulesList = source.getModulesList();
+ List ccaList = source.getCcasList();
+
+ for (DatedEvent datedEvent : datedEventList) {
+ datedEvents.add(new JsonAdaptedDatedEvent(datedEvent));
+ }
+
+ for (Module module : modulesList) {
+ modules.add(new JsonAdaptedModule(module));
+ }
+
+ for (Cca cca : ccaList) {
+ ccas.add(new JsonAdaptedCca(cca));
+ }
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted Schedule object into the model's {@code Schedule} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted schedule.
+ */
+ public List toModelTypeDatedEventList() throws IllegalValueException {
+ List datedEventList = new ArrayList<>();
+ for (JsonAdaptedDatedEvent datedEvent : datedEvents) {
+ datedEventList.add(datedEvent.toModelType());
+ }
+ return datedEventList;
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted Schedule object into the model's {@code Schedule} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted schedule.
+ */
+ public List toModelTypeModuleList() throws IllegalValueException {
+ List modulesList = new ArrayList<>();
+ for (JsonAdaptedModule module : modules) {
+ modulesList.add(module.toModelType());
+ }
+ return modulesList;
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted Schedule object into the model's {@code Schedule} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted schedule.
+ */
+ public List toModelTypeCcaList() throws IllegalValueException {
+ List ccaList = new ArrayList<>();
+ for (JsonAdaptedCca cca : ccas) {
+ ccaList.add(cca.toModelType());
+ }
+ return ccaList;
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted Schedule object into the model's {@code Schedule} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted schedule.
+ */
+ public Schedule toModelType() throws IllegalValueException {
+ List datedEventList = toModelTypeDatedEventList();
+ List modulesList = toModelTypeModuleList();
+ List ccaList = toModelTypeCcaList();
+
+ return new Schedule(modulesList, ccaList, datedEventList);
+ }
+}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java
index 3f16b2fcf26..f2c83246bb3 100644
--- a/src/main/java/seedu/address/ui/HelpWindow.java
+++ b/src/main/java/seedu/address/ui/HelpWindow.java
@@ -15,8 +15,8 @@
*/
public class HelpWindow extends UiPart {
- public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html";
- public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL;
+ public static final String USERGUIDE_URL = "https://ay2324s1-cs2103t-w12-4.github.io/tp/UserGuide.html";
+ public static final String HELP_MESSAGE = "For a comprehensive guide, please refer to our user guide here:";
private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
private static final String FXML = "HelpWindow.fxml";
@@ -27,6 +27,9 @@ public class HelpWindow extends UiPart {
@FXML
private Label helpMessage;
+ @FXML
+ private Label userGuideUrl;
+
/**
* Creates a new HelpWindow.
*
@@ -35,6 +38,7 @@ public class HelpWindow extends UiPart {
public HelpWindow(Stage root) {
super(FXML, root);
helpMessage.setText(HELP_MESSAGE);
+ userGuideUrl.setText(USERGUIDE_URL);
}
/**
diff --git a/src/main/java/seedu/address/ui/ListCellSelectedEvent.java b/src/main/java/seedu/address/ui/ListCellSelectedEvent.java
new file mode 100644
index 00000000000..91fa0563a95
--- /dev/null
+++ b/src/main/java/seedu/address/ui/ListCellSelectedEvent.java
@@ -0,0 +1,38 @@
+package seedu.address.ui;
+
+import javafx.event.Event;
+import javafx.event.EventType;
+import seedu.address.model.person.Person;
+
+/**
+ * The {@code ListCellSelectedEvent} class represents an event that is fired when a list cell is selected.
+ * It provides information about the selected person.
+ */
+public class ListCellSelectedEvent extends Event {
+ /**
+ * The event type for list cell selected events.
+ */
+ public static final EventType LIST_CELL_SELECTED =
+ new EventType<>(Event.ANY, "LIST_CELL_SELECTED");
+
+ private final Person selectedPerson;
+
+ /**
+ * Constructs a new {@code ListCellSelectedEvent} with the specified selected person.
+ *
+ * @param selectedPerson The selected person.
+ */
+ public ListCellSelectedEvent(Person selectedPerson) {
+ super(LIST_CELL_SELECTED);
+ this.selectedPerson = selectedPerson;
+ }
+
+ /**
+ * Gets the selected person associated with this event.
+ *
+ * @return The selected person.
+ */
+ public Person getSelectedPerson() {
+ return selectedPerson;
+ }
+}
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java
index 79e74ef37c0..54d186c78b2 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/seedu/address/ui/MainWindow.java
@@ -2,13 +2,18 @@
import java.util.logging.Logger;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
+import javafx.scene.Scene;
import javafx.scene.control.MenuItem;
+import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextInputControl;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
+import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
@@ -16,6 +21,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.person.Person;
/**
* The Main Window. Provides the basic application layout containing
@@ -34,6 +40,10 @@ public class MainWindow extends UiPart {
private PersonListPanel personListPanel;
private ResultDisplay resultDisplay;
private HelpWindow helpWindow;
+ private UserCard userProfile;
+ private Person selectedPerson;
+ private int selectedPersonPos;
+ private SelectedFriendCard friendProfile;
@FXML
private StackPane commandBoxPlaceholder;
@@ -50,6 +60,17 @@ public class MainWindow extends UiPart {
@FXML
private StackPane statusbarPlaceholder;
+ @FXML
+ private StackPane userProfilePlaceholder;
+ @FXML
+ private StackPane selectedFriendPlaceholder;
+ @FXML
+ private ScrollPane userCardScrollPane;
+ @FXML
+ private VBox input1;
+ @FXML
+ private VBox input2;
+
/**
* Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}.
*/
@@ -66,6 +87,7 @@ public MainWindow(Stage primaryStage, Logic logic) {
setAccelerators();
helpWindow = new HelpWindow();
+
}
public Stage getPrimaryStage() {
@@ -121,6 +143,36 @@ void fillInnerParts() {
CommandBox commandBox = new CommandBox(this::executeCommand);
commandBoxPlaceholder.getChildren().add(commandBox.getRoot());
+
+ userProfile = new UserCard(logic.getUser());
+ userProfilePlaceholder.getChildren().add(userProfile.getRoot());
+
+ Scene scene = primaryStage.getScene();
+
+ input1.setMinHeight(scene.getHeight() / 2);
+ input2.setMinHeight(scene.getHeight() / 2);
+
+ scene.heightProperty().addListener(new ChangeListener() {
+ @Override
+ public void changed(ObservableValue extends Number> observable,
+ Number oldSceneHeight, Number newSceneHeight) {
+ input1.setMinHeight((double) newSceneHeight / 2);
+ input2.setMinHeight((double) newSceneHeight / 2);
+ }
+ });
+
+ if (scene != null) {
+ scene.addEventFilter(ListCellSelectedEvent.LIST_CELL_SELECTED, event -> {
+ selectedPerson = event.getSelectedPerson();
+ if (selectedPerson != null) {
+ friendProfile = new SelectedFriendCard(selectedPerson);
+ selectedFriendPlaceholder.getChildren().clear();
+ selectedFriendPlaceholder.getChildren().add(friendProfile.getRoot());
+ selectedPersonPos = logic.getFilteredPersonList().indexOf(selectedPerson);
+ }
+ });
+ }
+
}
/**
@@ -186,11 +238,33 @@ private CommandResult executeCommand(String commandText) throws CommandException
handleExit();
}
+ if (commandResult.isCommonFreetime()) {
+
+ }
+
+ if (selectedPerson != null && commandResult.isRefresh()) {
+ friendProfile = new SelectedFriendCard(logic.getFilteredPersonList().get(selectedPersonPos));
+ selectedFriendPlaceholder.getChildren().clear();
+ selectedFriendPlaceholder.getChildren().add(friendProfile.getRoot());
+
+ userProfile = new UserCard(logic.getUser());
+ userProfilePlaceholder.getChildren().clear();
+ userProfilePlaceholder.getChildren().add(userProfile.getRoot());
+
+ } else if (commandResult.isRefresh()) {
+ userProfile = new UserCard(logic.getUser());
+ userProfilePlaceholder.getChildren().clear();
+ userProfilePlaceholder.getChildren().add(userProfile.getRoot());
+ }
+
return commandResult;
+
} catch (CommandException | ParseException e) {
logger.info("An error occurred while executing command: " + commandText);
resultDisplay.setFeedbackToUser(e.getMessage());
throw e;
}
+
}
+
}
diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java
index 094c42cda82..964d883bcca 100644
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ b/src/main/java/seedu/address/ui/PersonCard.java
@@ -39,6 +39,8 @@ public class PersonCard extends UiPart {
@FXML
private Label email;
@FXML
+ private Label birthday;
+ @FXML
private FlowPane tags;
/**
@@ -52,6 +54,7 @@ public PersonCard(Person person, int displayedIndex) {
phone.setText(person.getPhone().value);
address.setText(person.getAddress().value);
email.setText(person.getEmail().value);
+ birthday.setText("Birthday: " + person.getBirthday().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..7b3a6ccbf8f 100644
--- a/src/main/java/seedu/address/ui/PersonListPanel.java
+++ b/src/main/java/seedu/address/ui/PersonListPanel.java
@@ -15,10 +15,10 @@
*/
public class PersonListPanel extends UiPart {
private static final String FXML = "PersonListPanel.fxml";
- private final Logger logger = LogsCenter.getLogger(PersonListPanel.class);
-
@FXML
private ListView personListView;
+ private final Logger logger = LogsCenter.getLogger(PersonListPanel.class);
+
/**
* Creates a {@code PersonListPanel} with the given {@code ObservableList}.
@@ -42,6 +42,13 @@ protected void updateItem(Person person, boolean empty) {
setText(null);
} else {
setGraphic(new PersonCard(person, getIndex() + 1).getRoot());
+
+ setOnMouseClicked(event -> {
+ if (event.getClickCount() == 1) {
+ Person selectedPerson = getItem();
+ fireEvent(new ListCellSelectedEvent(selectedPerson));
+ }
+ });
}
}
}
diff --git a/src/main/java/seedu/address/ui/Reminder.java b/src/main/java/seedu/address/ui/Reminder.java
new file mode 100644
index 00000000000..99b4b9d2724
--- /dev/null
+++ b/src/main/java/seedu/address/ui/Reminder.java
@@ -0,0 +1,85 @@
+package seedu.address.ui;
+
+import javafx.geometry.Rectangle2D;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextArea;
+import javafx.scene.layout.VBox;
+import javafx.stage.Screen;
+import javafx.stage.Stage;
+import javafx.stage.Window;
+import seedu.address.model.Model;
+
+/**
+ * Reminder class that displays a dialog box with the user's reminded events and birthdays.
+ * The dialog box contains two sections, one for birthdays and one for schedule events.
+ * The dialog box is positioned at the bottom right corner of the primary screen.
+ */
+public class Reminder {
+ /**
+ * Displays a dialog box with reminders for birthdays and scheduled events.
+ *
+ * @param model The model containing the user's data.
+ * @param primaryStage The primary stage of the application.
+ */
+ public static void showReminder(Model model, Stage primaryStage) {
+
+ Dialog dialog = new Dialog<>();
+ dialog.setTitle("Reminders");
+
+ String remindedEvents = model.getUser().returnRemindedEvent();
+
+ String remindedBirthday = model.getBirthdayList();
+
+ // Create the first header label and content
+ Label header1 = new Label("Birthday");
+ header1.setStyle("-fx-font-size: 16px; -fx-font-weight: bold; -fx-text-fill: white;");
+ TextArea content1 = new TextArea(remindedBirthday);
+ content1.setStyle("-fx-font-size: 12px;");
+ content1.setEditable(false);
+ content1.setPrefColumnCount(25); // Adjust the preferred column count
+ content1.setPrefRowCount(3); // Adjust the preferred row count
+
+ // Create the second header label and content
+ Label header2 = new Label("Schedule");
+ header2.setStyle("-fx-font-size: 16px; -fx-font-weight: bold; -fx-text-fill: white;");
+ TextArea content2 = new TextArea(remindedEvents);
+ content2.setStyle("-fx-font-size: 12px;");
+ content2.setEditable(false);
+ content2.setPrefColumnCount(25); // Adjust the preferred column count
+ content2.setPrefRowCount(8); // Adjust the preferred row count
+
+ // Create a VBox to hold the components
+ VBox dialogVBox = new VBox(header1, content1, header2, content2);
+ dialog.getDialogPane().setContent(dialogVBox);
+
+ // Add OK and Cancel buttons
+ dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
+
+ // Get the primary screen dimensions
+ Screen screen = Screen.getPrimary();
+ Rectangle2D bounds = screen.getVisualBounds();
+
+ // Calculate the position for the bottom right corner
+ double x = bounds.getMaxX() - dialog.getDialogPane().getPrefWidth() - 340;
+ double y = bounds.getMaxY() - dialog.getDialogPane().getPrefHeight() - 390;
+
+ // Ensure the dialog stays within the screen boundaries
+ if (x < bounds.getMinX()) {
+ x = bounds.getMinX();
+ }
+ if (y < bounds.getMinY()) {
+ y = bounds.getMinY();
+ }
+
+ // Set the dialog's position
+ dialog.initOwner(primaryStage);
+ Window window = dialog.getDialogPane().getScene().getWindow();
+ window.setX(x);
+ window.setY(y);
+
+ // Show the dialog
+ dialog.showAndWait();
+ }
+}
diff --git a/src/main/java/seedu/address/ui/SelectedFriendCard.java b/src/main/java/seedu/address/ui/SelectedFriendCard.java
new file mode 100644
index 00000000000..f666fb11424
--- /dev/null
+++ b/src/main/java/seedu/address/ui/SelectedFriendCard.java
@@ -0,0 +1,228 @@
+package seedu.address.ui;
+
+import java.util.Comparator;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+import seedu.address.model.person.Person;
+
+/**
+ * The {@code SelectedFriendCard} class represents a card that displays detailed information
+ * about a selected friend (person).
+ * It is typically used to display the information of a friend when selected in the user interface.
+ */
+public class SelectedFriendCard extends UiPart {
+ private static final String FXML = "SelectedFriendCard.fxml";
+
+ public final Person selectedFriend;
+
+ @javafx.fxml.FXML
+ private Label userName;
+ @FXML
+ private Label phone;
+ @FXML
+ private Label address;
+ @FXML
+ private Label email;
+ @FXML
+ private Label birthday;
+ @FXML
+ private FlowPane tags;
+ @FXML
+ private VBox mondaySchedule;
+ @FXML
+ private VBox tuesdaySchedule;
+ @FXML
+ private VBox wednesdaySchedule;
+ @FXML
+ private VBox thursdaySchedule;
+ @FXML
+ private VBox fridaySchedule;
+ @FXML
+ private VBox saturdaySchedule;
+ @FXML
+ private VBox sundaySchedule;
+
+ /**
+ * Constructs a new {@code SelectedFriendCard} with the specified selected friend.
+ *
+ * @param selectedFriend The selected friend whose information is to be displayed on this card.
+ */
+ public SelectedFriendCard(Person selectedFriend) {
+ super(FXML);
+ this.selectedFriend = selectedFriend;
+ userName.setText(selectedFriend.getName().fullName);
+ phone.setText(selectedFriend.getPhone().value);
+ address.setText(selectedFriend.getAddress().value);
+ email.setText(selectedFriend.getEmail().value);
+ birthday.setText("Birthday: " + selectedFriend.getBirthday().toString());
+
+ selectedFriend.getTags().stream()
+ .sorted(Comparator.comparing(tag -> tag.tagName))
+ .forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
+
+ selectedFriend.getSchedule().getScheduleForDayOfWeek(1).stream()
+ .sorted(Comparator.comparing(timeBlock -> timeBlock.getTimeBlockString()))
+ .forEach(timeBlock -> {
+ Label label = new Label(timeBlock.getName() + "\n"
+ + timeBlock.getStartTime() + "-" + timeBlock.getEndTime());
+
+ mondaySchedule.getChildren().add(label);
+
+ switch (timeBlock.getType()) {
+ case "CCA":
+ label.getStyleClass().add("cca-time-block");
+ break;
+ case "Module":
+ label.getStyleClass().add("module-time-block");
+ break;
+ case "Event":
+ label.getStyleClass().add("event-time-block");
+ break;
+ default:
+ break;
+ }
+ });
+
+ selectedFriend.getSchedule().getScheduleForDayOfWeek(2).stream()
+ .sorted(Comparator.comparing(timeBlock -> timeBlock.getTimeBlockString()))
+ .forEach(timeBlock -> {
+ Label label = new Label(timeBlock.getName() + "\n"
+ + timeBlock.getStartTime() + "-" + timeBlock.getEndTime());
+
+ tuesdaySchedule.getChildren().add(label);
+
+ switch (timeBlock.getType()) {
+ case "CCA":
+ label.getStyleClass().add("cca-time-block");
+ break;
+ case "Module":
+ label.getStyleClass().add("module-time-block");
+ break;
+ case "Event":
+ label.getStyleClass().add("event-time-block");
+ break;
+ default:
+ break;
+ }
+ });
+
+ selectedFriend.getSchedule().getScheduleForDayOfWeek(3).stream()
+ .sorted(Comparator.comparing(timeBlock -> timeBlock.getTimeBlockString()))
+ .forEach(timeBlock -> {
+ Label label = new Label(timeBlock.getName() + "\n"
+ + timeBlock.getStartTime() + "-" + timeBlock.getEndTime());
+
+ wednesdaySchedule.getChildren().add(label);
+
+ switch (timeBlock.getType()) {
+ case "CCA":
+ label.getStyleClass().add("cca-time-block");
+ break;
+ case "Module":
+ label.getStyleClass().add("module-time-block");
+ break;
+ case "Event":
+ label.getStyleClass().add("event-time-block");
+ break;
+ default:
+ break;
+ }
+ });
+
+ selectedFriend.getSchedule().getScheduleForDayOfWeek(4).stream()
+ .sorted(Comparator.comparing(timeBlock -> timeBlock.getTimeBlockString()))
+ .forEach(timeBlock -> {
+ Label label = new Label(timeBlock.getName() + "\n"
+ + timeBlock.getStartTime() + "-" + timeBlock.getEndTime());
+
+ thursdaySchedule.getChildren().add(label);
+
+ switch (timeBlock.getType()) {
+ case "CCA":
+ label.getStyleClass().add("cca-time-block");
+ break;
+ case "Module":
+ label.getStyleClass().add("module-time-block");
+ break;
+ case "Event":
+ label.getStyleClass().add("event-time-block");
+ break;
+ default:
+ break;
+ }
+ });
+
+ selectedFriend.getSchedule().getScheduleForDayOfWeek(5).stream()
+ .sorted(Comparator.comparing(timeBlock -> timeBlock.getTimeBlockString()))
+ .forEach(timeBlock -> {
+ Label label = new Label(timeBlock.getName() + "\n"
+ + timeBlock.getStartTime() + "-" + timeBlock.getEndTime());
+
+ fridaySchedule.getChildren().add(label);
+
+ switch (timeBlock.getType()) {
+ case "CCA":
+ label.getStyleClass().add("cca-time-block");
+ break;
+ case "Module":
+ label.getStyleClass().add("module-time-block");
+ break;
+ case "Event":
+ label.getStyleClass().add("event-time-block");
+ break;
+ default:
+ break;
+ }
+ });
+
+ selectedFriend.getSchedule().getScheduleForDayOfWeek(6).stream()
+ .sorted(Comparator.comparing(timeBlock -> timeBlock.getTimeBlockString()))
+ .forEach(timeBlock -> {
+ Label label = new Label(timeBlock.getName() + "\n"
+ + timeBlock.getStartTime() + "-" + timeBlock.getEndTime());
+
+ saturdaySchedule.getChildren().add(label);
+
+ switch (timeBlock.getType()) {
+ case "CCA":
+ label.getStyleClass().add("cca-time-block");
+ break;
+ case "Module":
+ label.getStyleClass().add("module-time-block");
+ break;
+ case "Event":
+ label.getStyleClass().add("event-time-block");
+ break;
+ default:
+ break;
+ }
+ });
+
+ selectedFriend.getSchedule().getScheduleForDayOfWeek(7).stream()
+ .sorted(Comparator.comparing(timeBlock -> timeBlock.getTimeBlockString()))
+ .forEach(timeBlock -> {
+ Label label = new Label(timeBlock.getName() + "\n"
+ + timeBlock.getStartTime() + "-" + timeBlock.getEndTime());
+
+ sundaySchedule.getChildren().add(label);
+
+ switch (timeBlock.getType()) {
+ case "CCA":
+ label.getStyleClass().add("cca-time-block");
+ break;
+ case "Module":
+ label.getStyleClass().add("module-time-block");
+ break;
+ case "Event":
+ label.getStyleClass().add("event-time-block");
+ break;
+ default:
+ break;
+ }
+ });
+ }
+}
diff --git a/src/main/java/seedu/address/ui/UserCard.java b/src/main/java/seedu/address/ui/UserCard.java
new file mode 100644
index 00000000000..971cc2b5b1f
--- /dev/null
+++ b/src/main/java/seedu/address/ui/UserCard.java
@@ -0,0 +1,229 @@
+package seedu.address.ui;
+
+import java.util.Comparator;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+import seedu.address.model.person.Person;
+
+/**
+ * Represents a user card in the GUI.
+ * Displays the user's name, phone number, address, email, free times, and tags.
+ */
+public class UserCard extends UiPart {
+
+ private static final String FXML = "UserCard.fxml";
+
+ public final Person user;
+
+ @FXML
+ private Label userName;
+ @FXML
+ private Label phone;
+ @FXML
+ private Label address;
+ @FXML
+ private Label email;
+ @FXML
+ private Label birthday;
+ @FXML
+ private FlowPane tags;
+ @FXML
+ private VBox mondaySchedule;
+ @FXML
+ private VBox tuesdaySchedule;
+ @FXML
+ private VBox wednesdaySchedule;
+ @FXML
+ private VBox thursdaySchedule;
+ @FXML
+ private VBox fridaySchedule;
+ @FXML
+ private VBox saturdaySchedule;
+ @FXML
+ private VBox sundaySchedule;
+
+ /**
+ * Creates a new UserCard with the given Person object.
+ * @param user The Person object to display in the card.
+ */
+ public UserCard(Person user) {
+ super(FXML);
+ this.user = user;
+ userName.setText(user.getName().fullName);
+ phone.setText(user.getPhone().value);
+ address.setText(user.getAddress().value);
+ email.setText(user.getEmail().value);
+ birthday.setText("Birthday: " + user.getBirthday().toString());
+ user.getTags().stream()
+ .sorted(Comparator.comparing(tag -> tag.tagName))
+ .forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
+
+ user.getSchedule().getScheduleForDayOfWeek(1).stream()
+ .sorted(Comparator.comparing(timeBlock -> timeBlock.getTimeBlockString()))
+ .forEach(timeBlock -> {
+ Label label = new Label(timeBlock.getName() + "\n"
+ + timeBlock.getStartTime() + "-" + timeBlock.getEndTime());
+
+ mondaySchedule.getChildren().add(label);
+
+ switch (timeBlock.getType()) {
+ case "CCA":
+ label.getStyleClass().add("cca-time-block");
+ break;
+ case "Module":
+ label.getStyleClass().add("module-time-block");
+ break;
+ case "Event":
+ label.getStyleClass().add("event-time-block");
+ break;
+ default:
+ break;
+ }
+ });
+
+ user.getSchedule().getScheduleForDayOfWeek(2).stream()
+ .sorted(Comparator.comparing(timeBlock -> timeBlock.getTimeBlockString()))
+ .forEach(timeBlock -> {
+ Label label = new Label(timeBlock.getName() + "\n"
+ + timeBlock.getStartTime() + "-" + timeBlock.getEndTime());
+
+ tuesdaySchedule.getChildren().add(label);
+
+ switch (timeBlock.getType()) {
+ case "CCA":
+ label.getStyleClass().add("cca-time-block");
+ break;
+ case "Module":
+ label.getStyleClass().add("module-time-block");
+ break;
+ case "Event":
+ label.getStyleClass().add("event-time-block");
+ break;
+ default:
+ break;
+ }
+ });
+
+ user.getSchedule().getScheduleForDayOfWeek(3).stream()
+ .sorted(Comparator.comparing(timeBlock -> timeBlock.getTimeBlockString()))
+ .forEach(timeBlock -> {
+ Label label = new Label(timeBlock.getName() + "\n"
+ + timeBlock.getStartTime() + "-" + timeBlock.getEndTime());
+
+ wednesdaySchedule.getChildren().add(label);
+
+ switch (timeBlock.getType()) {
+ case "CCA":
+ label.getStyleClass().add("cca-time-block");
+ break;
+ case "Module":
+ label.getStyleClass().add("module-time-block");
+ break;
+ case "Event":
+ label.getStyleClass().add("event-time-block");
+ break;
+ default:
+ break;
+ }
+ });
+
+ user.getSchedule().getScheduleForDayOfWeek(4).stream()
+ .sorted(Comparator.comparing(timeBlock -> timeBlock.getTimeBlockString()))
+ .forEach(timeBlock -> {
+ Label label = new Label(timeBlock.getName() + "\n"
+ + timeBlock.getStartTime() + "-" + timeBlock.getEndTime());
+
+ thursdaySchedule.getChildren().add(label);
+
+ switch (timeBlock.getType()) {
+ case "CCA":
+ label.getStyleClass().add("cca-time-block");
+ break;
+ case "Module":
+ label.getStyleClass().add("module-time-block");
+ break;
+ case "Event":
+ label.getStyleClass().add("event-time-block");
+ break;
+ default:
+ break;
+ }
+ });
+
+ user.getSchedule().getScheduleForDayOfWeek(5).stream()
+ .sorted(Comparator.comparing(timeBlock -> timeBlock.getTimeBlockString()))
+ .forEach(timeBlock -> {
+ Label label = new Label(timeBlock.getName() + "\n"
+ + timeBlock.getStartTime() + "-" + timeBlock.getEndTime());
+
+ fridaySchedule.getChildren().add(label);
+
+ switch (timeBlock.getType()) {
+ case "CCA":
+ label.getStyleClass().add("cca-time-block");
+ break;
+ case "Module":
+ label.getStyleClass().add("module-time-block");
+ break;
+ case "Event":
+ label.getStyleClass().add("event-time-block");
+ break;
+ default:
+ break;
+ }
+ });
+
+ user.getSchedule().getScheduleForDayOfWeek(6).stream()
+ .sorted(Comparator.comparing(timeBlock -> timeBlock.getTimeBlockString()))
+ .forEach(timeBlock -> {
+ Label label = new Label(timeBlock.getName() + "\n"
+ + timeBlock.getStartTime() + "-" + timeBlock.getEndTime());
+
+ saturdaySchedule.getChildren().add(label);
+
+ switch (timeBlock.getType()) {
+ case "CCA":
+ label.getStyleClass().add("cca-time-block");
+ break;
+ case "Module":
+ label.getStyleClass().add("module-time-block");
+ break;
+ case "Event":
+ label.getStyleClass().add("event-time-block");
+ break;
+ default:
+ break;
+ }
+ });
+
+ user.getSchedule().getScheduleForDayOfWeek(7).stream()
+ .sorted(Comparator.comparing(timeBlock -> timeBlock.getTimeBlockString()))
+ .forEach(timeBlock -> {
+ Label label = new Label(timeBlock.getName() + "\n"
+ + timeBlock.getStartTime() + "-" + timeBlock.getEndTime());
+
+ sundaySchedule.getChildren().add(label);
+
+ switch (timeBlock.getType()) {
+ case "CCA":
+ label.getStyleClass().add("cca-time-block");
+ break;
+ case "Module":
+ label.getStyleClass().add("module-time-block");
+ break;
+ case "Event":
+ label.getStyleClass().add("event-time-block");
+ break;
+ default:
+ break;
+ }
+ });
+
+
+ }
+
+}
diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css
index 36e6b001cd8..3554508dcb0 100644
--- a/src/main/resources/view/DarkTheme.css
+++ b/src/main/resources/view/DarkTheme.css
@@ -350,3 +350,94 @@
-fx-background-radius: 2;
-fx-font-size: 11;
}
+
+#freeTimes {
+ -fx-hgap: 7;
+ -fx-vgap: 3;
+}
+
+#freeTimes .label {
+ -fx-text-fill: white;
+ -fx-background-color: #6b358c;
+ -fx-padding: 1 3 1 3;
+ -fx-border-radius: 2;
+ -fx-background-radius: 2;
+ -fx-font-size: 11;
+}
+
+#userProfilePlaceholder .label {
+ -fx-text-fill: white;
+}
+
+.ListView {
+ -fx-font-family: "Segoe UI";
+ -fx-font-size: 13px;
+ -fx-text-fill: #010504;
+}
+
+#selectedFriendPlaceholder .label {
+ -fx-text-fill: white;
+}
+
+.display_small_label {
+ -fx-font-family: "Segoe UI";
+ -fx-font-size: 13px;
+ -fx-text-fill: #010504;
+}
+
+.gridPane_display {
+ -fx-grid-lines-visible: true;
+ -fx-stroke: white;
+ -fx-max-width: 100%;
+}
+
+.gridPane_display Line {
+ -fx-stroke: white;
+}
+
+.timetable_heading {
+ -fx-font-size: 14px;
+}
+
+.timetable-timeslot .label {
+ -fx-label-padding: 3 5;
+ -fx-border-radius: 5px;
+ -fx-background-radius: 5px;
+ -fx-font-size: 13px;
+ -fx-text-alignment: center;
+ -fx-overrun: ellipsis;
+}
+
+
+.card-display {
+ -fx-background-color: derive(#1d1d1d, 20%);
+ -fx-border-color: derive(#1d1d1d, 10%);
+ -fx-border-top-width: 1px;
+}
+
+.cca-time-block {
+ -fx-background-color: #9e4f4f;
+}
+
+.module-time-block {
+ -fx-background-color: #3866b0;
+}
+
+.event-time-block {
+ -fx-background-color: #488263;
+}
+
+.background-needed {
+ -fx-background-color: #1d1d1d;
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/HelpWindow.css b/src/main/resources/view/HelpWindow.css
index 17e8a8722cd..a675b4e9e67 100644
--- a/src/main/resources/view/HelpWindow.css
+++ b/src/main/resources/view/HelpWindow.css
@@ -17,3 +17,31 @@
#helpMessageContainer {
-fx-background-color: derive(#1d1d1d, 20%);
}
+
+.help_heading {
+ -fx-text-fill: white;
+ -fx-font-size: 25px;
+}
+
+.normal_text {
+ -fx-text-fill: white;
+ -fx-font-size: 15px;
+}
+
+.user_guide_prompt {
+ -fx-background-color: #3d4c57;
+ -fx-border-style: solid;
+ -fx-border-radius: 10px;
+ -fx-border-width: 0px;
+ -fx-background-radius: 10px;
+}
+
+.help_sub_heading {
+ -fx-text-fill: white;
+ -fx-font-size: 18px;
+
+}
+
+.separator {
+ -fx-border-width: 10px;
+}
diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml
index e01f330de33..dc881e1f89f 100644
--- a/src/main/resources/view/HelpWindow.fxml
+++ b/src/main/resources/view/HelpWindow.fxml
@@ -9,6 +9,8 @@
+
+
@@ -19,24 +21,59 @@
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
index 7778f666a0a..9ab68535548 100644
--- a/src/main/resources/view/MainWindow.fxml
+++ b/src/main/resources/view/MainWindow.fxml
@@ -10,11 +10,18 @@
+
+
+
+
+
+
+
+ title="TimetaBRO" minWidth="1200" minHeight="800" onCloseRequest="#handleExit">
-
+
@@ -23,38 +30,72 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml
index f5e812e25e6..46bb416562e 100644
--- a/src/main/resources/view/PersonListCard.fxml
+++ b/src/main/resources/view/PersonListCard.fxml
@@ -31,6 +31,7 @@
+
diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml
index a1bb6bbace8..a2e0bee8bae 100644
--- a/src/main/resources/view/PersonListPanel.fxml
+++ b/src/main/resources/view/PersonListPanel.fxml
@@ -4,5 +4,5 @@
-
+
diff --git a/src/main/resources/view/SelectedFriendCard.fxml b/src/main/resources/view/SelectedFriendCard.fxml
new file mode 100644
index 00000000000..6f68e045eaa
--- /dev/null
+++ b/src/main/resources/view/SelectedFriendCard.fxml
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/UserCard.fxml b/src/main/resources/view/UserCard.fxml
new file mode 100644
index 00000000000..0226696246d
--- /dev/null
+++ b/src/main/resources/view/UserCard.fxml
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
index a7427fe7aa2..4fef99847f1 100644
--- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
@@ -4,11 +4,25 @@
"phone": "94351253",
"email": "alice@example.com",
"address": "123, Jurong West Ave 6, #08-111",
+ "birthday": "2000-01-01",
+ "schedule" : {
+ "datedEvents" : [ ],
+ "modules" : [ ],
+ "ccas" : [ ],
+ "meetUpEvents" : [ ]
+ },
"tags": [ "friends" ]
}, {
"name": "Alice Pauline",
"phone": "94351253",
"email": "pauline@example.com",
- "address": "4th street"
+ "address": "4th street",
+ "birthday": "2000-01-01",
+ "schedule" : {
+ "datedEvents" : [ ],
+ "modules" : [ ],
+ "ccas" : [ ],
+ "meetUpEvents" : [ ]
+ }
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
index ad3f135ae42..6afc045b455 100644
--- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json
@@ -3,6 +3,7 @@
"name": "Hans Muster",
"phone": "9482424",
"email": "invalid@email!3e",
- "address": "4th street"
+ "address": "4th street",
+ "birthday": "2000-01-01"
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/smallTypicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/smallTypicalPersonsAddressBook.json
new file mode 100644
index 00000000000..c1527b24707
--- /dev/null
+++ b/src/test/data/JsonSerializableAddressBookTest/smallTypicalPersonsAddressBook.json
@@ -0,0 +1,27 @@
+{
+ "_comment": "AddressBook save file which contains the same Person values as in TypicalPersons#getTypicalAddressBook()",
+ "persons" : [ {
+ "name" : "George Best",
+ "phone" : "94824424",
+ "email" : "anna@example.com",
+ "address" : "4th street",
+ "birthday": "2000-01-01",
+ "schedule" : {
+ "datedEvents" : [ {
+ "name" : "CS2103 Meeting",
+ "reminder" : true,
+ "timeBlockString" : "Tuesday 1030 1130",
+ "localDateString" : "2023-10-10"
+ } ],
+ "modules" : [ {
+ "name" : "CS2103",
+ "timeBlockString" : "Wednesday 1200 1300"
+ } ],
+ "ccas" : [ {
+ "name" : "Basketball",
+ "timeBlockString" : "Monday 1800 2000"
+ } ]
+ },
+ "tags" : [ ]
+ } ]
+}
diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
index 72262099d35..f5203b89037 100644
--- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
@@ -5,42 +5,127 @@
"phone" : "94351253",
"email" : "alice@example.com",
"address" : "123, Jurong West Ave 6, #08-111",
+ "birthday": "2000-01-01",
+ "schedule" : {
+ "datedEvents" : [ ],
+ "modules" : [ ],
+ "ccas" : [ ]
+ },
"tags" : [ "friends" ]
}, {
"name" : "Benson Meier",
"phone" : "98765432",
"email" : "johnd@example.com",
"address" : "311, Clementi Ave 2, #02-25",
+ "birthday": "2000-01-01",
+ "schedule" : {
+ "datedEvents" : [ ],
+ "modules" : [ ],
+ "ccas" : [ ]
+ },
"tags" : [ "owesMoney", "friends" ]
}, {
"name" : "Carl Kurz",
"phone" : "95352563",
"email" : "heinz@example.com",
"address" : "wall street",
+ "birthday": "2000-01-01",
+ "schedule" : {
+ "datedEvents" : [ ],
+ "modules" : [ ],
+ "ccas" : [ ]
+ },
"tags" : [ ]
}, {
"name" : "Daniel Meier",
"phone" : "87652533",
"email" : "cornelia@example.com",
"address" : "10th street",
+ "birthday": "2000-01-01",
+ "schedule" : {
+ "datedEvents" : [ {
+ "name" : "CS2103 Meeting",
+ "reminder" : true,
+ "timeBlockString" : "Tuesday 1030 1130",
+ "localDateString" : "2023-10-10"
+ } ],
+ "modules" : [ {
+ "name" : "CS2103",
+ "timeBlockString" : "Wednesday 1200 1300"
+ } ],
+ "ccas" : [ {
+ "name" : "Basketball",
+ "timeBlockString" : "Monday 1800 2000"
+ } ]
+ },
"tags" : [ "friends" ]
}, {
"name" : "Elle Meyer",
- "phone" : "9482224",
+ "phone" : "94822243",
"email" : "werner@example.com",
"address" : "michegan ave",
+ "birthday": "2000-01-01", "schedule" : {
+ "datedEvents" : [ {
+ "name" : "CS2103 Meeting",
+ "reminder" : true,
+ "timeBlockString" : "Tuesday 1030 1130",
+ "localDateString" : "2023-10-10"
+ } ],
+ "modules" : [ {
+ "name" : "CS2103",
+ "timeBlockString" : "Wednesday 1200 1300"
+ } ],
+ "ccas" : [ {
+ "name" : "Basketball",
+ "timeBlockString" : "Monday 1800 2000"
+ } ]
+ },
"tags" : [ ]
}, {
"name" : "Fiona Kunz",
- "phone" : "9482427",
+ "phone" : "94824272",
"email" : "lydia@example.com",
"address" : "little tokyo",
+ "birthday": "2000-01-01",
+ "schedule" : {
+ "datedEvents" : [ {
+ "name" : "CS2103 Meeting",
+ "reminder" : true,
+ "timeBlockString" : "Tuesday 1030 1130",
+ "localDateString" : "2023-10-10"
+ } ],
+ "modules" : [ {
+ "name" : "CS2103",
+ "timeBlockString" : "Wednesday 1200 1300"
+ } ],
+ "ccas" : [ {
+ "name" : "Basketball",
+ "timeBlockString" : "Monday 1800 2000"
+ } ]
+ },
"tags" : [ ]
}, {
"name" : "George Best",
- "phone" : "9482442",
+ "phone" : "94824424",
"email" : "anna@example.com",
"address" : "4th street",
+ "birthday": "2000-01-01",
+ "schedule" : {
+ "datedEvents" : [ {
+ "name" : "CS2103 Meeting",
+ "reminder" : true,
+ "timeBlockString" : "Tuesday 1030 1130",
+ "localDateString" : "2023-10-10"
+ } ],
+ "modules" : [ {
+ "name" : "CS2103",
+ "timeBlockString" : "Wednesday 1200 1300"
+ } ],
+ "ccas" : [ {
+ "name" : "Basketball",
+ "timeBlockString" : "Monday 1800 2000"
+ } ]
+ },
"tags" : [ ]
} ]
}
diff --git a/src/test/data/JsonUserDataStorageTest/EmptyUserData.json b/src/test/data/JsonUserDataStorageTest/EmptyUserData.json
new file mode 100644
index 00000000000..0db3279e44b
--- /dev/null
+++ b/src/test/data/JsonUserDataStorageTest/EmptyUserData.json
@@ -0,0 +1,3 @@
+{
+
+}
diff --git a/src/test/data/JsonUserDataStorageTest/ExtraValuesUserData.json b/src/test/data/JsonUserDataStorageTest/ExtraValuesUserData.json
new file mode 100644
index 00000000000..034a9dad089
--- /dev/null
+++ b/src/test/data/JsonUserDataStorageTest/ExtraValuesUserData.json
@@ -0,0 +1,17 @@
+{
+ "user" : {
+ "name" : "Amy Bee",
+ "phone" : "85355255",
+ "email" : "amy@gmail.com",
+ "address" : "123, Jurong West Ave 6, #08-111",
+ "birthday": "2000-01-01",
+ "schedule" : {
+ "datedEvents" : [ ],
+ "modules" : [ ],
+ "ccas" : [ ],
+ "meetUpEvents" : [ ]
+ },
+ "datedEvents" : [ ],
+ "telegram" : "me"
+ }
+}
diff --git a/src/test/data/JsonUserDataStorageTest/InvalidUserData.json b/src/test/data/JsonUserDataStorageTest/InvalidUserData.json
new file mode 100644
index 00000000000..804ab866a8f
--- /dev/null
+++ b/src/test/data/JsonUserDataStorageTest/InvalidUserData.json
@@ -0,0 +1,10 @@
+{
+ "user" : {
+ "name" : "Amy Bee",
+ "phone" : "85355255",
+ "email" : "amy@gmail.com",
+ "address" : "123, Jurong West Ave 6, #08-111",
+ "birthday": "2000-01-01",
+ "freeTimes" : [ "Monday 1234 1334"]
+ }
+}
diff --git a/src/test/data/JsonUserDataStorageTest/NotJsonFormatUserData.json b/src/test/data/JsonUserDataStorageTest/NotJsonFormatUserData.json
new file mode 100644
index 00000000000..b738f344942
--- /dev/null
+++ b/src/test/data/JsonUserDataStorageTest/NotJsonFormatUserData.json
@@ -0,0 +1 @@
+Not a json file!
diff --git a/src/test/data/JsonUserDataStorageTest/TypicalUserData.json b/src/test/data/JsonUserDataStorageTest/TypicalUserData.json
new file mode 100644
index 00000000000..640864f1834
--- /dev/null
+++ b/src/test/data/JsonUserDataStorageTest/TypicalUserData.json
@@ -0,0 +1,16 @@
+{
+ "user" : {
+ "name" : "Amy Bee",
+ "phone" : "85355255",
+ "email" : "amy@gmail.com",
+ "address" : "123, Jurong West Ave 6, #08-111",
+ "birthday": "2000-01-01",
+ "schedule" : {
+ "datedEvents" : [ ],
+ "modules" : [ ],
+ "ccas" : [ ],
+ "meetUpEvents" : [ ]
+ },
+ "datedEvents" : [ ]
+ }
+}
diff --git a/src/test/java/seedu/address/AppParametersTest.java b/src/test/java/seedu/address/AppParametersTest.java
index 133cc008bce..5bad2b3458c 100644
--- a/src/test/java/seedu/address/AppParametersTest.java
+++ b/src/test/java/seedu/address/AppParametersTest.java
@@ -86,4 +86,17 @@ public Map getNamed() {
return Collections.unmodifiableMap(namedParameters);
}
}
+
+ @Test
+ public void testHashCode() {
+ AppParameters appParameters1 = new AppParameters();
+ AppParameters appParameters2 = new AppParameters();
+
+ // Set the same configPath for both objects
+ appParameters1.setConfigPath(Paths.get("test_config_path"));
+ appParameters2.setConfigPath(Paths.get("test_config_path"));
+
+ assertEquals(appParameters1.hashCode(), appParameters2.hashCode(),
+ "Hash codes of equal AppParameters should be the same");
+ }
}
diff --git a/src/test/java/seedu/address/commons/core/ConfigTest.java b/src/test/java/seedu/address/commons/core/ConfigTest.java
index d3ba2a52a89..02eca9e49fd 100644
--- a/src/test/java/seedu/address/commons/core/ConfigTest.java
+++ b/src/test/java/seedu/address/commons/core/ConfigTest.java
@@ -1,6 +1,7 @@
package seedu.address.commons.core;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -23,5 +24,24 @@ public void equalsMethod() {
assertTrue(defaultConfig.equals(defaultConfig));
}
+ @Test
+ public void testEqualsWithNull() {
+ Config config1 = new Config();
+ Config config2 = null;
+ assertFalse(config1.equals(config2));
+ }
+
+ @Test
+ public void testHashCode() {
+ Config config1 = new Config();
+ Config config2 = new Config();
+
+ int hashCode1 = config1.hashCode();
+ int hashCode2 = config2.hashCode();
+
+ assertEquals(hashCode1, hashCode2, "Hash codes should be equal for equal objects");
+ }
+
+
}
diff --git a/src/test/java/seedu/address/commons/core/GuiSettingsTest.java b/src/test/java/seedu/address/commons/core/GuiSettingsTest.java
index b7876c4349d..8a0d99438cf 100644
--- a/src/test/java/seedu/address/commons/core/GuiSettingsTest.java
+++ b/src/test/java/seedu/address/commons/core/GuiSettingsTest.java
@@ -1,6 +1,7 @@
package seedu.address.commons.core;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import org.junit.jupiter.api.Test;
@@ -13,4 +14,21 @@ public void toStringMethod() {
+ guiSettings.getWindowCoordinates() + "}";
assertEquals(expected, guiSettings.toString());
}
+
+ @Test
+ public void testEqualsWithNull() {
+ GuiSettings settings = new GuiSettings();
+ assertFalse(settings.equals(null));
+ }
+
+ @Test
+ public void testHashCode() {
+ GuiSettings settings1 = new GuiSettings();
+ GuiSettings settings2 = new GuiSettings();
+
+ int hashCode1 = settings1.hashCode();
+ int hashCode2 = settings2.hashCode();
+
+ assertEquals(hashCode1, hashCode2, "Hash codes should be equal for equal objects");
+ }
}
diff --git a/src/test/java/seedu/address/commons/core/LogsCenterTest.java b/src/test/java/seedu/address/commons/core/LogsCenterTest.java
new file mode 100644
index 00000000000..c517c85d92d
--- /dev/null
+++ b/src/test/java/seedu/address/commons/core/LogsCenterTest.java
@@ -0,0 +1,19 @@
+package seedu.address.commons.core;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.logging.Level;
+
+import org.junit.jupiter.api.Test;
+
+public class LogsCenterTest {
+
+ @Test
+ public void testInit() {
+ Config config = new Config();
+ config.setLogLevel(Level.INFO);
+ LogsCenter.init(config);
+ assertEquals(Level.INFO, LogsCenter.getCurrentLogLevel());
+ }
+
+}
diff --git a/src/test/java/seedu/address/commons/core/VersionTest.java b/src/test/java/seedu/address/commons/core/VersionTest.java
index 495cd231554..7c4b93ef0c1 100644
--- a/src/test/java/seedu/address/commons/core/VersionTest.java
+++ b/src/test/java/seedu/address/commons/core/VersionTest.java
@@ -1,6 +1,7 @@
package seedu.address.commons.core;
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.testutil.Assert.assertThrows;
@@ -132,4 +133,33 @@ private void verifyVersionParsedCorrectly(String versionString,
int major, int minor, int patch, boolean isEarlyAccess) {
assertEquals(new Version(major, minor, patch, isEarlyAccess), Version.fromString(versionString));
}
+
+ @Test
+ public void testCompareToMethod() {
+ Version version1 = new Version(2, 1, 4, false);
+ Version version2 = new Version(2, 1, 4, false);
+ Version version3 = new Version(2, 1, 3, false);
+ Version version4 = new Version(1, 2, 3, true);
+
+ int result1 = version1.compareTo(version2); // Should return 0 (equal)
+ int result2 = version1.compareTo(version3); // Should return 1 (greater)
+ int result3 = version1.compareTo(version4); // Should return 1 (greater)
+ int result4 = version3.compareTo(version1); // Should return -1 (smaller)
+
+ assertEquals(0, result1, "compareTo should return 0 when versions are equal");
+ assertEquals(1, result2, "compareTo should return 1 when version1 is greater");
+ assertEquals(1, result3, "compareTo should return 1 when version1 is greater");
+ assertEquals(-1, result4, "compareTo should return -1 when version3 is smaller");
+ }
+
+ @Test
+ public void testEqualsMethod() {
+ Version version1 = new Version(2, 1, 4, false);
+ Version version2 = new Version(2, 1, 4, false);
+ Version version3 = null;
+
+ assertTrue(version1.equals(version1), "A version should be equal to itself");
+ assertTrue(version1.equals(version2), "Two versions with the same values should be equal");
+ assertFalse(version1.equals(version3), "Two different versions should not be equal");
+ }
}
diff --git a/src/test/java/seedu/address/commons/util/ToStringBuilderTest.java b/src/test/java/seedu/address/commons/util/ToStringBuilderTest.java
new file mode 100644
index 00000000000..3db99283700
--- /dev/null
+++ b/src/test/java/seedu/address/commons/util/ToStringBuilderTest.java
@@ -0,0 +1,60 @@
+package seedu.address.commons.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class ToStringBuilderTest {
+
+ private ToStringBuilder builder;
+
+ @BeforeEach
+ public void setUp() {
+ builder = new ToStringBuilder("TestObject");
+ }
+
+ @Test
+ public void addField_singleField_success() {
+ builder.add("fieldName", "fieldValue");
+ String expected = "TestObject{" + "fieldName=fieldValue" + "}";
+ assertEquals(expected, builder.toString());
+ }
+
+ @Test
+ public void addField_multipleFields_success() {
+ builder.add("field1", "value1")
+ .add("field2", "value2")
+ .add("field3", "value3");
+ String expected = "TestObject{" + "field1=value1, field2=value2, field3=value3" + "}";
+ assertEquals(expected, builder.toString());
+ }
+
+ @Test
+ public void addField_emptyBuilder_noFields() {
+ String expected = "TestObject{}";
+ assertEquals(expected, builder.toString());
+ }
+
+ @Test
+ public void addFieldWithNullValue_success() {
+ builder.add("nullField", null);
+ String expected = "TestObject{" + "nullField=null" + "}";
+ assertEquals(expected, builder.toString());
+ }
+
+ @Test
+ public void addFieldWithSpecialCharacters_success() {
+ builder.add("specialField", "#$%^&*");
+ String expected = "TestObject{" + "specialField=#$%^&*" + "}";
+ assertEquals(expected, builder.toString());
+ }
+
+ @Test
+ public void addFieldWithSpaces_success() {
+ builder.add("spaceField", "This is a value with spaces");
+ String expected = "TestObject{" + "spaceField=This is a value with spaces" + "}";
+ assertEquals(expected, builder.toString());
+ }
+}
+
diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java
index baf8ce336a2..47bea99e8a2 100644
--- a/src/test/java/seedu/address/logic/LogicManagerTest.java
+++ b/src/test/java/seedu/address/logic/LogicManagerTest.java
@@ -4,6 +4,7 @@
import static seedu.address.logic.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;
import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.BIRTHDAY_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
@@ -18,6 +19,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
+import seedu.address.commons.core.GuiSettings;
import seedu.address.logic.commands.AddCommand;
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.ListCommand;
@@ -26,9 +28,10 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.UserPrefs;
import seedu.address.model.person.Person;
+import seedu.address.model.user.UserPrefs;
import seedu.address.storage.JsonAddressBookStorage;
+import seedu.address.storage.JsonUserDataStorage;
import seedu.address.storage.JsonUserPrefsStorage;
import seedu.address.storage.StorageManager;
import seedu.address.testutil.PersonBuilder;
@@ -47,8 +50,9 @@ public class LogicManagerTest {
public void setUp() {
JsonAddressBookStorage addressBookStorage =
new JsonAddressBookStorage(temporaryFolder.resolve("addressBook.json"));
+ JsonUserDataStorage userDataStorage = new JsonUserDataStorage(temporaryFolder.resolve("userData.json"));
JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json"));
- StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage, userDataStorage);
logic = new LogicManager(model, storage);
}
@@ -123,7 +127,7 @@ private void assertCommandException(String inputCommand, String expectedMessage)
*/
private void assertCommandFailure(String inputCommand, Class extends Throwable> expectedException,
String expectedMessage) {
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), model.getUserData());
assertCommandFailure(inputCommand, expectedException, expectedMessage, expectedModel);
}
@@ -160,16 +164,40 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath)
JsonUserPrefsStorage userPrefsStorage =
new JsonUserPrefsStorage(temporaryFolder.resolve("ExceptionUserPrefs.json"));
- StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ JsonUserDataStorage userDataStorage = new JsonUserDataStorage(temporaryFolder
+ .resolve("ExceptionUserData.json"));
+ StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage, userDataStorage);
logic = new LogicManager(model, storage);
// Triggers the saveAddressBook method by executing an add command
String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY
- + EMAIL_DESC_AMY + ADDRESS_DESC_AMY;
+ + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + BIRTHDAY_DESC_AMY;
Person expectedPerson = new PersonBuilder(AMY).withTags().build();
ModelManager expectedModel = new ModelManager();
expectedModel.addPerson(expectedPerson);
assertCommandFailure(addCommand, CommandException.class, expectedMessage, expectedModel);
}
+
+ @Test
+ public void getAddressBookFilePath() {
+ assertEquals(model.getAddressBookFilePath(), logic.getAddressBookFilePath());
+ }
+
+ @Test
+ public void getAddressBook() {
+ assertEquals(model.getAddressBook(), logic.getAddressBook());
+ }
+
+ @Test
+ public void getUser() {
+ assertEquals(model.getUser(), logic.getUser());
+ }
+
+ @Test
+ public void setGuiSettings() {
+ model.setGuiSettings(new GuiSettings());
+ logic.setGuiSettings(new GuiSettings());
+ assertEquals(model.getGuiSettings(), logic.getGuiSettings());
+ }
}
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
index 162a0c86031..745b1e7f84e 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
@@ -10,8 +10,9 @@
import seedu.address.logic.Messages;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
-import seedu.address.model.UserPrefs;
import seedu.address.model.person.Person;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
import seedu.address.testutil.PersonBuilder;
/**
@@ -23,14 +24,14 @@ public class AddCommandIntegrationTest {
@BeforeEach
public void setUp() {
- model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
}
@Test
public void execute_newPerson_success() {
Person validPerson = new PersonBuilder().build();
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new UserData());
expectedModel.addPerson(validPerson);
assertCommandSuccess(new AddCommand(validPerson), model,
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
index 90e8253f48e..03a32a0eb9f 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java
@@ -6,6 +6,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
+import static seedu.address.testutil.TypicalPersons.BENSON;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -21,8 +22,11 @@
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
+import seedu.address.model.user.ReadOnlyUserData;
+import seedu.address.model.user.ReadOnlyUserPrefs;
+import seedu.address.model.user.User;
import seedu.address.testutil.PersonBuilder;
public class AddCommandTest {
@@ -53,6 +57,27 @@ public void execute_duplicatePerson_throwsCommandException() {
assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_PERSON, () -> addCommand.execute(modelStub));
}
+ @Test
+ public void execute_duplicatePhone_throwsCommandException() {
+ Person validPerson = new PersonBuilder(BENSON).withPhone("11111111").build();
+ AddCommand addCommand = new AddCommand(validPerson);
+ ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded();
+ modelStub.addPerson(new PersonBuilder(ALICE).withPhone("11111111").build());
+
+ assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_PHONE, () -> addCommand.execute(modelStub));
+ }
+
+ @Test
+ public void execute_duplicateEmail_throwsCommandException() {
+ Person validPerson = new PersonBuilder(BENSON).withEmail("xyz@example.com").build();
+ AddCommand addCommand = new AddCommand(validPerson);
+ ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded();
+ modelStub.addPerson(new PersonBuilder(ALICE).withEmail("xyz@example.com").build());
+
+ assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_EMAIL, () -> addCommand.execute(modelStub));
+ }
+
+
@Test
public void equals() {
Person alice = new PersonBuilder().withName("Alice").build();
@@ -148,6 +173,11 @@ public void setPerson(Person target, Person editedPerson) {
throw new AssertionError("This method should not be called.");
}
+ @Override
+ public String getBirthdayList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
@Override
public ObservableList getFilteredPersonList() {
throw new AssertionError("This method should not be called.");
@@ -157,6 +187,47 @@ public ObservableList getFilteredPersonList() {
public void updateFilteredPersonList(Predicate predicate) {
throw new AssertionError("This method should not be called.");
}
+
+ @Override
+ public void setUserData(ReadOnlyUserData userData) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyUserData getUserData() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public User getUser() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setUser(User user) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ObservableList getUserView() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Person getPersonWithName(Name name) {
+ return null;
+ }
+
+ @Override
+ public boolean hasPhone(Person person) {
+ throw new AssertionError("This method should not be called");
+ }
+
+ @Override
+ public boolean hasEmail(Person person) {
+ throw new AssertionError("This method should not be called");
+ }
+
}
/**
@@ -189,6 +260,18 @@ public boolean hasPerson(Person person) {
return personsAdded.stream().anyMatch(person::isSamePerson);
}
+ @Override
+ public boolean hasPhone(Person person) {
+ requireNonNull(person);
+ return personsAdded.stream().anyMatch(person::isSamePhone);
+ }
+
+ @Override
+ public boolean hasEmail(Person person) {
+ requireNonNull(person);
+ return personsAdded.stream().anyMatch(person::isSameEmail);
+ }
+
@Override
public void addPerson(Person person) {
requireNonNull(person);
diff --git a/src/test/java/seedu/address/logic/commands/AddEventCommandTest.java b/src/test/java/seedu/address/logic/commands/AddEventCommandTest.java
new file mode 100644
index 00000000000..28b0f112bd6
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/AddEventCommandTest.java
@@ -0,0 +1,66 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.commands.AddEventCommand.MESSAGE_SUCCESS;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.person.Person;
+import seedu.address.model.user.User;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
+import seedu.address.testutil.UserBuilder;
+
+public class AddEventCommandTest {
+ private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+
+ @Test
+ public void execute_validDated_success() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ AddEventCommand addEventCommand = new AddEventCommand("CS2103 Meeting",
+ "2023-10-10 1030 1130", "y");
+ String expectedMessage = MESSAGE_SUCCESS + ("\nDated Event:\n" + "CS2103 Meeting"
+ + " " + "2023-10-10 1030 1130" + " to " + newUser.getName());
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new UserData());
+ expectedModel.setUser(newUser);
+
+ assertCommandSuccess(addEventCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_validDated_friendSuccess() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ Person friend = model.getFilteredPersonList().get(0);
+ AddEventCommand addEventCommand = new AddEventCommand("CS2103 Meeting",
+ Index.fromZeroBased(0), "2023-10-10 1030 1130", "y");
+ String expectedMessage = MESSAGE_SUCCESS + ("\nDated Event:\n" + "CS2103 Meeting"
+ + " " + "2023-10-10 1030 1130" + " to " + friend.getName());
+ Model expectedModel = new ModelManager(model.getAddressBook(),
+ new UserPrefs(), new UserData());
+ expectedModel.setUser(newUser);
+
+ assertCommandSuccess(addEventCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidIndex_failure() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ AddEventCommand addEventCommand = new AddEventCommand("Walk Dog",
+ Index.fromZeroBased(100), "2023-10-10 1030 1130", "n");
+
+ String expectedMessage = Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX + "\n"
+ + "Index can be max " + 7 + "!";
+
+ assertCommandFailure(addEventCommand, model, expectedMessage);
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/AddScheduleCommandTest.java b/src/test/java/seedu/address/logic/commands/AddScheduleCommandTest.java
new file mode 100644
index 00000000000..cf394c1b9f3
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/AddScheduleCommandTest.java
@@ -0,0 +1,112 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.commands.AddEventCommand.MESSAGE_SUCCESS;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.person.Person;
+import seedu.address.model.user.User;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
+import seedu.address.testutil.UserBuilder;
+
+public class AddScheduleCommandTest {
+ private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+
+ @Test
+ public void execute_validCCa_success() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ AddScheduleCommand addScheduleCommand = new AddScheduleCommand("Basketball",
+ "cca", "Monday 1030 1130");
+ String expectedMessage = MESSAGE_SUCCESS + ("\nCCA:\n" + "Basketball"
+ + " " + "Monday 1030 1130" + " to " + newUser.getName());
+ Model expectedModel = new ModelManager(model.getAddressBook(),
+ new UserPrefs(), new UserData());
+ expectedModel.setUser(newUser);
+
+ assertCommandSuccess(addScheduleCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_validCca_friendSuccess() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ Person friend = model.getFilteredPersonList().get(0);
+ AddScheduleCommand addScheduleCommand = new AddScheduleCommand("Basketball",
+ "cca", Index.fromZeroBased(0), "Monday 1030 1130");
+ String expectedMessage = MESSAGE_SUCCESS + ("\nCCA:\n" + "Basketball"
+ + " " + "Monday 1030 1130" + " to " + friend.getName());
+ Model expectedModel = new ModelManager(model.getAddressBook(),
+ new UserPrefs(), new UserData());
+ expectedModel.setUser(newUser);
+
+ assertCommandSuccess(addScheduleCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_validModule_success() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ AddScheduleCommand addScheduleCommand = new AddScheduleCommand("CS2103",
+ "module", "Monday 1030 1130");
+ String expectedMessage = MESSAGE_SUCCESS + "\nModule:\n" + "CS2103"
+ + " " + "Monday 1030 1130" + " to " + newUser.getName();
+
+ Model expectedModel = new ModelManager(model.getAddressBook(),
+ new UserPrefs(), new UserData());
+ expectedModel.setUser(newUser);
+
+ assertCommandSuccess(addScheduleCommand, model, expectedMessage, expectedModel);
+ }
+ @Test
+ public void execute_validModuleFriend_success() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ Person friend = model.getFilteredPersonList().get(0);
+ AddScheduleCommand addScheduleCommand = new AddScheduleCommand("CS2103",
+ "module", Index.fromZeroBased(0), "Saturday 0000 0100");
+ String expectedMessage = MESSAGE_SUCCESS + "\nModule:\n" + "CS2103"
+ + " " + "Saturday 0000 0100" + " to " + friend.getName();
+
+ Model expectedModel = new ModelManager(model.getAddressBook(),
+ new UserPrefs(), new UserData());
+ expectedModel.setUser(newUser);
+
+ assertCommandSuccess(addScheduleCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidEventType_failure() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ AddScheduleCommand addScheduleCommand = new AddScheduleCommand("CS2103",
+ "sleep", "Monday 1030 1130");
+
+ String expectedMessage = "Invalid event type!"
+ + "\n Event type can only be 'Module' or 'CCA'";
+
+ assertCommandFailure(addScheduleCommand, model, expectedMessage);
+ }
+
+ @Test
+ public void execute_invalidIndex_failure() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ AddScheduleCommand addScheduleCommand = new AddScheduleCommand("CS2103",
+ "module", Index.fromZeroBased(100), "Monday 1030 1130");
+
+ String expectedMessage = Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX + "\n"
+ + "Index can be max " + "7" + "!";
+
+ assertCommandFailure(addScheduleCommand, model, expectedMessage);
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
index 80d9110c03a..6fa08ac2608 100644
--- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
@@ -8,7 +8,8 @@
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
-import seedu.address.model.UserPrefs;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
public class ClearCommandTest {
@@ -22,8 +23,8 @@ public void execute_emptyAddressBook_success() {
@Test
public void execute_nonEmptyAddressBook_success() {
- Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+ Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
expectedModel.setAddressBook(new AddressBook());
assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel);
diff --git a/src/test/java/seedu/address/logic/commands/CommandResultTest.java b/src/test/java/seedu/address/logic/commands/CommandResultTest.java
index 7b8c7cd4546..ea67f745d78 100644
--- a/src/test/java/seedu/address/logic/commands/CommandResultTest.java
+++ b/src/test/java/seedu/address/logic/commands/CommandResultTest.java
@@ -8,13 +8,26 @@
import org.junit.jupiter.api.Test;
public class CommandResultTest {
+
+ @Test
+ public void isRefresh() {
+ CommandResult commandResult = new CommandResult("feedback");
+ CommandResult commandResultWithRefresh = new CommandResult("feedback", false, false, true, false);
+
+ assertTrue(commandResultWithRefresh.isRefresh());
+ assertFalse(commandResult.isRefresh());
+ }
+
@Test
public void equals() {
CommandResult commandResult = new CommandResult("feedback");
+ CommandResult commandResultWithCft = new CommandResult("feedback", true);
// same values -> returns true
assertTrue(commandResult.equals(new CommandResult("feedback")));
- assertTrue(commandResult.equals(new CommandResult("feedback", false, false)));
+ assertTrue(commandResult.equals(new CommandResult("feedback", false, false, false, false)));
+ assertTrue(commandResultWithCft.equals(new CommandResult("feedback", true)));
+ assertTrue(commandResultWithCft.equals(new CommandResult("feedback", false, false, false, true)));
// same object -> returns true
assertTrue(commandResult.equals(commandResult));
@@ -29,10 +42,10 @@ public void equals() {
assertFalse(commandResult.equals(new CommandResult("different")));
// different showHelp value -> returns false
- assertFalse(commandResult.equals(new CommandResult("feedback", true, false)));
+ assertFalse(commandResult.equals(new CommandResult("feedback", true, false, false, false)));
// different exit value -> returns false
- assertFalse(commandResult.equals(new CommandResult("feedback", false, true)));
+ assertFalse(commandResult.equals(new CommandResult("feedback", false, true, false, false)));
}
@Test
@@ -46,10 +59,10 @@ public void hashcode() {
assertNotEquals(commandResult.hashCode(), new CommandResult("different").hashCode());
// different showHelp value -> returns different hashcode
- assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", true, false).hashCode());
+ assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", true, false, false, false).hashCode());
// different exit value -> returns different hashcode
- assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false, true).hashCode());
+ assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false, true, false, false).hashCode());
}
@Test
diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
index 643a1d08069..f956e61c554 100644
--- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
+++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
@@ -3,23 +3,29 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
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_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalDatedEvents.NORMAL_DATEDEVENTS;
+import static seedu.address.testutil.TypicalSchedule.NORMAL_SCHEDULE;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.edit.EditPersonDescriptor;
+import seedu.address.logic.commands.edit.EditUserDescriptor;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.Person;
import seedu.address.testutil.EditPersonDescriptorBuilder;
+import seedu.address.testutil.EditUserDescriptorBuilder;
/**
* Contains helper methods for testing commands.
@@ -36,6 +42,8 @@ public class CommandTestUtil {
public static final String VALID_ADDRESS_BOB = "Block 123, Bobby Street 3";
public static final String VALID_TAG_HUSBAND = "husband";
public static final String VALID_TAG_FRIEND = "friend";
+ public static final String VALID_BIRTHDAY_AMY = "2000-01-01";
+ public static final String VALID_BIRTHDAY_BOB = "2000-02-02";
public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY;
public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB;
@@ -45,6 +53,8 @@ public class CommandTestUtil {
public static final String EMAIL_DESC_BOB = " " + PREFIX_EMAIL + VALID_EMAIL_BOB;
public static final String ADDRESS_DESC_AMY = " " + PREFIX_ADDRESS + VALID_ADDRESS_AMY;
public static final String ADDRESS_DESC_BOB = " " + PREFIX_ADDRESS + VALID_ADDRESS_BOB;
+ public static final String BIRTHDAY_DESC_AMY = " " + PREFIX_BIRTHDAY + VALID_BIRTHDAY_AMY;
+ public static final String BIRTHDAY_DESC_BOB = " " + PREFIX_BIRTHDAY + VALID_BIRTHDAY_BOB;
public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND;
public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND;
@@ -52,21 +62,32 @@ public class CommandTestUtil {
public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones
public static final String INVALID_EMAIL_DESC = " " + PREFIX_EMAIL + "bob!yahoo"; // missing '@' symbol
public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses
+ public static final String INVALID_BIRTHDAY_DESC = " " + PREFIX_BIRTHDAY; // empty string not allowed for addresses
public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags
- public static final String PREAMBLE_WHITESPACE = "\t \r \n";
+ public static final String PREAMBLE_WHITESPACE = " ";
public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble";
- public static final EditCommand.EditPersonDescriptor DESC_AMY;
- public static final EditCommand.EditPersonDescriptor DESC_BOB;
+ public static final EditPersonDescriptor DESC_AMY;
+ public static final EditPersonDescriptor DESC_BOB;
+ public static final EditUserDescriptor DESC_USER_AMY;
+ public static final EditUserDescriptor DESC_USER_BOB;
static {
DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY)
.withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY)
- .withTags(VALID_TAG_FRIEND).build();
+ .withSchedule().withBirthday(VALID_BIRTHDAY_AMY).withTags(VALID_TAG_FRIEND).build();
DESC_BOB = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB)
.withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB)
+ .withSchedule(NORMAL_SCHEDULE).withBirthday(VALID_BIRTHDAY_BOB)
.withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
+ DESC_USER_AMY = new EditUserDescriptorBuilder().withName(VALID_NAME_AMY)
+ .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY)
+ .withSchedule().withBirthday(VALID_BIRTHDAY_AMY).withTags(VALID_TAG_FRIEND).build();
+ DESC_USER_BOB = new EditUserDescriptorBuilder().withName(VALID_NAME_BOB)
+ .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB)
+ .withSchedule(NORMAL_SCHEDULE).withBirthday(VALID_BIRTHDAY_BOB)
+ .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).withDatedEvents(NORMAL_DATEDEVENTS).build();
}
/**
@@ -120,7 +141,7 @@ public static void showPersonAtIndex(Model model, Index targetIndex) {
Person person = model.getFilteredPersonList().get(targetIndex.getZeroBased());
final String[] splitName = person.getName().fullName.split("\\s+");
- model.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(splitName[0])));
+ model.updateFilteredPersonList(new NameContainsKeywordsPredicate(Collections.singletonList(splitName[0])));
assertEquals(1, model.getFilteredPersonList().size());
}
diff --git a/src/test/java/seedu/address/logic/commands/CommonFreeTimeCommandTest.java b/src/test/java/seedu/address/logic/commands/CommonFreeTimeCommandTest.java
new file mode 100644
index 00000000000..fb781a20c3b
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/CommonFreeTimeCommandTest.java
@@ -0,0 +1,84 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalUsers.JANE;
+import static seedu.address.testutil.TypicalUsers.JOSH;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
+
+/**
+ * Contains integration tests (interaction with the Model) for {@code CommonFreeTimeCommand}.
+ */
+
+public class CommonFreeTimeCommandTest {
+ private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+ private final Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+
+ @BeforeEach
+ public void setUp() {
+ model.setUser(JANE);
+ expectedModel.setUser(JANE);
+ }
+
+ @Test
+ public void execute_userNoFreetime_failure() {
+ model.setUser(JOSH);
+ expectedModel.setUser(JOSH);
+ CommonFreetimeCommand commonFreetimeCommand = new CommonFreetimeCommand();
+ assertCommandFailure(commonFreetimeCommand, model, CommonFreetimeCommand.MESSAGE_NO_FREE_TIME);
+ }
+
+ @Test
+ public void execute_nameFriend_success() {
+ CommonFreetimeCommand commonFreetimeCommand = new CommonFreetimeCommand(Index.fromOneBased(2));
+ String expectedMessage = "You have common free times with Benson Meier at:" + "\n"
+ + "[Monday 0000 1800]" + "\n" + "[Monday 2000 2400]" + "\n"
+ + "[Tuesday 0000 2400]" + "\n" + "[Wednesday 0000 1200]" + "\n" + "[Wednesday 1300 2400]" + "\n"
+ + "[Thursday 0000 2400]" + "\n" + "[Friday 0000 2400]" + "\n" + "[Saturday 0000 2400]" + "\n"
+ + "[Sunday 0000 2400]" + "\n";
+ assertCommandSuccess(commonFreetimeCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void equals() {
+ final CommonFreetimeCommand standardCommand = new CommonFreetimeCommand(Index.fromOneBased(1));
+
+ final CommonFreetimeCommand standardAllCommand = new CommonFreetimeCommand();
+ // same values -> returns true
+ CommonFreetimeCommand commandWithSameValues = new CommonFreetimeCommand(Index.fromOneBased(1));
+ assertEquals(standardCommand, commandWithSameValues);
+
+ // same object -> returns true
+ assertEquals(standardCommand, standardCommand);
+
+ assertEquals(new CommonFreetimeCommand(), standardAllCommand);
+
+ // null -> returns false
+ assertNotEquals(null, standardCommand);
+
+ // different types -> returns false
+ assertNotEquals(standardCommand, new ClearCommand());
+
+ // different descriptor -> returns false
+ assertNotEquals(standardCommand, new CommonFreetimeCommand(Index.fromOneBased(2)));
+ }
+
+ @Test
+ public void toStringMethod() {
+ CommonFreetimeCommand commonFreetimeCommandAlice = new CommonFreetimeCommand(Index.fromOneBased(1));
+ String expected = CommonFreetimeCommand.class.getCanonicalName()
+ + "{index=" + Index.class.getCanonicalName() + "{zeroBasedIndex=" + 0 + "}}";
+ assertEquals(expected, commonFreetimeCommandAlice.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
index b6f332eabca..0ab7899d8bb 100644
--- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
@@ -16,8 +16,9 @@
import seedu.address.logic.Messages;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
-import seedu.address.model.UserPrefs;
import seedu.address.model.person.Person;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
/**
* Contains integration tests (interaction with the Model) and unit tests for
@@ -25,7 +26,7 @@
*/
public class DeleteCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
@Test
public void execute_validIndexUnfilteredList_success() {
@@ -35,7 +36,7 @@ public void execute_validIndexUnfilteredList_success() {
String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS,
Messages.format(personToDelete));
- ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new UserData());
expectedModel.deletePerson(personToDelete);
assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel);
@@ -59,7 +60,7 @@ public void execute_validIndexFilteredList_success() {
String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS,
Messages.format(personToDelete));
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new UserData());
expectedModel.deletePerson(personToDelete);
showNoPerson(expectedModel);
diff --git a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java
index 9533c473875..83c6f375e6f 100644
--- a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java
@@ -14,7 +14,8 @@ public class ExitCommandTest {
@Test
public void execute_exit_success() {
- CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
+ CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false,
+ true, false, false);
assertCommandSuccess(new ExitCommand(), model, expectedCommandResult, expectedModel);
}
}
diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
index b8b7dbba91a..eb2cf15b420 100644
--- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
@@ -17,15 +17,16 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
-import seedu.address.model.UserPrefs;
import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
/**
* Contains integration tests (interaction with the Model) for {@code FindCommand}.
*/
public class FindCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
@Test
public void equals() {
diff --git a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java
index 4904fc4352e..ab3eb4e25a7 100644
--- a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java
@@ -14,7 +14,7 @@ public class HelpCommandTest {
@Test
public void execute_help_success() {
- CommandResult expectedCommandResult = new CommandResult(SHOWING_HELP_MESSAGE, true, false);
+ CommandResult expectedCommandResult = new CommandResult(SHOWING_HELP_MESSAGE, true, false, false, false);
assertCommandSuccess(new HelpCommand(), model, expectedCommandResult, expectedModel);
}
}
diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/address/logic/commands/ListCommandTest.java
index 435ff1f7275..5b92b97d39c 100644
--- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ListCommandTest.java
@@ -10,7 +10,8 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
-import seedu.address.model.UserPrefs;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
/**
* Contains integration tests (interaction with the Model) and unit tests for ListCommand.
@@ -22,8 +23,8 @@ public class ListCommandTest {
@BeforeEach
public void setUp() {
- model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+ expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs(), new UserData());
}
@Test
diff --git a/src/test/java/seedu/address/logic/commands/RemoveEventCommandTest.java b/src/test/java/seedu/address/logic/commands/RemoveEventCommandTest.java
new file mode 100644
index 00000000000..b8b575f963f
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/RemoveEventCommandTest.java
@@ -0,0 +1,108 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.util.ArrayList;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.timetable.DatedEvent;
+import seedu.address.model.user.User;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
+import seedu.address.testutil.UserBuilder;
+
+public class RemoveEventCommandTest {
+ private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+
+ @Test
+ public void execute_validDated_success() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ model.getUser().getSchedule().addDatedEvent(DatedEvent.newDatedEvent("CS2103 Meeting 2023-10-10 1030 1130 y"));
+ RemoveEventCommand removeEventCommand = new RemoveEventCommand("CS2103 Meeting", null);
+ String expectedMessage = "Dated Event 'CS2103 Meeting' deleted from your calendar!";
+ Model expectedModel = new ModelManager(model.getAddressBook(),
+ new UserPrefs(), new UserData());
+ expectedModel.setUser(newUser);
+ expectedModel.getUser().getSchedule().addDatedEvent(DatedEvent
+ .newDatedEvent("CS2103 Meeting 2023-10-10 1030 1130 y"));
+
+ assertCommandSuccess(removeEventCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_validDated_friendSuccess() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ Person friend = model.getFilteredPersonList().get(0);
+ friend.getSchedule().addDatedEvent(DatedEvent.newDatedEvent("CS2103 Meeting 2023-10-10 1030 1130 y"));
+ RemoveEventCommand removeEventCommand = new RemoveEventCommand("CS2103 Meeting",
+ Index.fromZeroBased(0));
+ String expectedMessage = "Dated Event 'CS2103 Meeting' deleted from Alice Pauline's calendar!";
+ Model expectedModel = new ModelManager(model.getAddressBook(),
+ new UserPrefs(), new UserData());
+ expectedModel.setUser(newUser);
+ Person expectedFriend = expectedModel.getFilteredPersonList().get(0);
+ expectedFriend.getSchedule().addDatedEvent(DatedEvent.newDatedEvent("CS2103 Meeting 2023-10-10 1030 1130 y"));
+
+ assertCommandSuccess(removeEventCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidEventName_failure() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ model.getUser().getSchedule().addDatedEvent(DatedEvent.newDatedEvent("CS2103 Meeting 2023-10-10 1030 1130 y"));
+ RemoveEventCommand removeEventCommand = new RemoveEventCommand("CS2101", null);
+
+ String expectedMessage = "Event " + "CS2101" + " does not exist!\n"
+ + "Please check that you have entered the correct event name!\n";
+
+ assertCommandFailure(removeEventCommand, model, expectedMessage);
+ }
+
+ @Test
+ public void execute_invalidIndex_failure() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ model.getUser().getSchedule().addDatedEvent(DatedEvent.newDatedEvent("CS2103 Meeting 2023-10-10 1030 1130 y"));
+ RemoveEventCommand removeEventCommand = new RemoveEventCommand("CS2103 Meeting",
+ Index.fromZeroBased(1000));
+
+ String expectedMessage = Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX + "\n"
+ + "Index can be max " + "7" + "!";
+
+ assertCommandFailure(removeEventCommand, model, expectedMessage);
+ }
+
+ @Test
+ public void listEvents_success() {
+ RemoveEventCommand removeEventCommand = new RemoveEventCommand("CS2103", null);
+ ArrayList events = new ArrayList<>();
+ events.add(DatedEvent.newDatedEvent("CS2103 Meeting 2023-10-10 1030 1130 y"));
+ events.add(DatedEvent.newDatedEvent("Walk Dog 2023-10-10 1030 1130 n"));
+ events.add(DatedEvent.newDatedEvent("Competitive sleeping 2023-10-10 1030 1130 y"));
+ String expectedMessage = "DatedEvent: [CS2103 Meeting] on 2023-10-10 [Tuesday 1030 1130] Reminder: Yes\n"
+ + "DatedEvent: [Walk Dog] on 2023-10-10 [Tuesday 1030 1130] Reminder: No\n"
+ + "DatedEvent: [Competitive sleeping] on 2023-10-10 [Tuesday 1030 1130] Reminder: Yes\n";
+ assertEquals(expectedMessage, removeEventCommand.listEvents(events));
+ }
+
+ @Test
+ public void listEvents_empty() {
+ RemoveEventCommand removeEventCommand = new RemoveEventCommand("CS2103", null);
+ ArrayList events = new ArrayList<>();
+ String expectedMessage = "";
+ assertEquals(expectedMessage, removeEventCommand.listEvents(events));
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/RemoveReminderCommandTest.java b/src/test/java/seedu/address/logic/commands/RemoveReminderCommandTest.java
new file mode 100644
index 00000000000..52379b179fe
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/RemoveReminderCommandTest.java
@@ -0,0 +1,71 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalUsers.JANE;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.user.User;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
+import seedu.address.testutil.UserBuilder;
+
+public class RemoveReminderCommandTest {
+ private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+
+ @Test
+ public void execute_validEvent_success() {
+ User newUser = new UserBuilder(JANE).build();
+ model.setUser(newUser);
+ RemoveReminderCommand removeReminderCommand = new RemoveReminderCommand("CS2103 Meeting");
+ String expectedMessage = RemoveReminderCommand.MESSAGE_REMOVE_REMINDER_SUCCESS + "CS2103 Meeting";
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()),
+ new UserPrefs(), new UserData());
+ expectedModel.setUser(newUser);
+
+ assertCommandSuccess(removeReminderCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_noSuchEvent_failure() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ RemoveReminderCommand removeReminderCommand = new RemoveReminderCommand("CS2101 Meeting");
+ String expectedMessage = RemoveReminderCommand.MESSAGE_NO_EVENT;
+
+ assertCommandFailure(removeReminderCommand, model, expectedMessage);
+ }
+
+ @Test
+ public void equals() {
+ model.setUser(new UserBuilder().build());
+ final RemoveReminderCommand standardCommand = new RemoveReminderCommand("CS2103 Meeting");
+
+ // same object -> returns true
+ assertEquals(standardCommand, standardCommand);
+
+ // null -> returns false
+ assertNotEquals(null, standardCommand);
+
+ // different types -> returns false
+ assertNotEquals(standardCommand, new ClearCommand());
+
+ // different descriptor -> returns false
+ assertNotEquals(standardCommand, new RemoveReminderCommand("Walk Dog"));
+ }
+
+ @Test
+ public void toStringTest() {
+ RemoveReminderCommand removeReminderCommand = new RemoveReminderCommand("CS2103 Meeting");
+ String expected = RemoveReminderCommand.class.getCanonicalName() + "{" + "Remove Reminder for="
+ + "CS2103 Meeting" + "}";
+ assertEquals(expected, removeReminderCommand.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/RemoveScheduleCommandTest.java b/src/test/java/seedu/address/logic/commands/RemoveScheduleCommandTest.java
new file mode 100644
index 00000000000..8a967553e1e
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/RemoveScheduleCommandTest.java
@@ -0,0 +1,160 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+
+import java.util.ArrayList;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.person.Person;
+import seedu.address.model.person.timetable.Cca;
+import seedu.address.model.person.timetable.DatedEvent;
+import seedu.address.model.person.timetable.Module;
+import seedu.address.model.user.User;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
+import seedu.address.testutil.UserBuilder;
+
+public class RemoveScheduleCommandTest {
+ private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+
+ @Test
+ public void execute_validCca_success() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ model.getUser().getSchedule().addCca(new Cca("Basketball", "Monday 1800 2000"));
+ RemoveScheduleCommand removeScheduleCommand = new RemoveScheduleCommand("Basketball",
+ "cca", null);
+ String expectedMessage = "CCA 'Basketball' deleted from your calendar!";
+ Model expectedModel = new ModelManager(model.getAddressBook(),
+ new UserPrefs(), new UserData());
+ expectedModel.setUser(newUser);
+ expectedModel.getUser().getSchedule().addCca(new Cca("Basketball", "Monday 1800 2000"));
+
+ assertCommandSuccess(removeScheduleCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_validCca_friendSuccess() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ Person friend = model.getFilteredPersonList().get(0);
+ friend.getSchedule().addCca(new Cca("Basketball", "Monday 1800 2000"));
+ RemoveScheduleCommand removeScheduleCommand = new RemoveScheduleCommand("Basketball",
+ "cca", Index.fromZeroBased(0));
+ String expectedMessage = "CCA 'Basketball' deleted from Alice Pauline's calendar!";
+ Model expectedModel = new ModelManager(model.getAddressBook(),
+ new UserPrefs(), new UserData());
+ expectedModel.setUser(newUser);
+ Person expectedFriend = expectedModel.getFilteredPersonList().get(0);
+ expectedFriend.getSchedule().addCca(new Cca("Basketball", "Monday 1800 2000"));
+
+ assertCommandSuccess(removeScheduleCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_validModule_success() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ model.getUser().getSchedule().addModule(new Module("CS2103", "Monday 1800 2000"));
+ RemoveScheduleCommand removeScheduleCommand = new RemoveScheduleCommand("CS2103",
+ "module", null);
+ String expectedMessage = "Module 'CS2103' deleted from your calendar!";
+ Model expectedModel = new ModelManager(model.getAddressBook(),
+ new UserPrefs(), new UserData());
+ expectedModel.setUser(newUser);
+ expectedModel.getUser().getSchedule().addModule(new Module("CS2103", "Monday 1800 2000"));
+
+ assertCommandSuccess(removeScheduleCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_validModule_friendSuccess() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ Person friend = model.getFilteredPersonList().get(0);
+ friend.getSchedule().addModule(new Module("CS2103", "Monday 1800 2000"));
+ RemoveScheduleCommand removeScheduleCommand = new RemoveScheduleCommand("CS2103",
+ "module", Index.fromZeroBased(0));
+ String expectedMessage = "Module 'CS2103' deleted from Alice Pauline's calendar!";
+ Model expectedModel = new ModelManager(model.getAddressBook(),
+ new UserPrefs(), new UserData());
+ expectedModel.setUser(newUser);
+ Person expectedFriend = expectedModel.getFilteredPersonList().get(0);
+ expectedFriend.getSchedule().addModule(new Module("CS2103", "Monday 1800 2000"));
+
+ assertCommandSuccess(removeScheduleCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidEventType_failure() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ model.getUser().getSchedule().addModule(new Module("CS2103", "Monday 1800 2000"));
+ RemoveScheduleCommand removeScheduleCommand = new RemoveScheduleCommand("CS2103",
+ "sleep");
+
+ String expectedMessage = "Invalid event type!\n" + "Event type must either be 'cca' or 'module'!\n";
+
+ assertCommandFailure(removeScheduleCommand, model, expectedMessage);
+ }
+
+ @Test
+ public void execute_invalidEventName_failure() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ model.getUser().getSchedule().addModule(new Module("CS2103", "Monday 1800 2000"));
+ RemoveScheduleCommand removeScheduleCommand = new RemoveScheduleCommand("CS1101",
+ "module");
+
+ String expectedMessage = "Module " + "CS1101" + " does not exist!\n"
+ + "Please check that you have entered the correct module name!\n";
+
+ assertCommandFailure(removeScheduleCommand, model, expectedMessage);
+ }
+
+ @Test
+ public void execute_invalidFriendIndex_failure() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ model.getUser().getSchedule().addModule(new Module("CS2103", "Monday 1800 2000"));
+ RemoveScheduleCommand removeScheduleCommand = new RemoveScheduleCommand("CS2103",
+ "module", Index.fromZeroBased(1000));
+
+ String expectedMessage = Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX + "\n"
+ + "Index can be max " + "7" + "!";
+
+ assertCommandFailure(removeScheduleCommand, model, expectedMessage);
+ }
+
+ @Test
+ public void listEvents_success() {
+ RemoveScheduleCommand removeScheduleCommand = new RemoveScheduleCommand("CS2103",
+ "module");
+ ArrayList events = new ArrayList<>();
+ events.add(DatedEvent.newDatedEvent("CS2103 Meeting 2023-10-10 1030 1130 y"));
+ events.add(DatedEvent.newDatedEvent("Walk Dog 2023-10-10 1030 1130 n"));
+ events.add(DatedEvent.newDatedEvent("Competitive sleeping 2023-10-10 1030 1130 y"));
+ String expectedMessage = "DatedEvent: [CS2103 Meeting] on 2023-10-10 [Tuesday 1030 1130] Reminder: Yes\n"
+ + "DatedEvent: [Walk Dog] on 2023-10-10 [Tuesday 1030 1130] Reminder: No\n"
+ + "DatedEvent: [Competitive sleeping] on 2023-10-10 [Tuesday 1030 1130] Reminder: Yes\n";
+ assertEquals(expectedMessage, removeScheduleCommand.listEvents(events));
+ }
+
+ @Test
+ public void listEvents_empty() {
+ RemoveScheduleCommand removeScheduleCommand = new RemoveScheduleCommand("CS2103",
+ "module");
+ ArrayList events = new ArrayList<>();
+ String expectedMessage = "";
+ assertEquals(expectedMessage, removeScheduleCommand.listEvents(events));
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/SetReminderCommandTest.java b/src/test/java/seedu/address/logic/commands/SetReminderCommandTest.java
new file mode 100644
index 00000000000..c3189bd5def
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/SetReminderCommandTest.java
@@ -0,0 +1,71 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalUsers.JANE;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.user.User;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
+import seedu.address.testutil.UserBuilder;
+
+public class SetReminderCommandTest {
+ private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+
+ @Test
+ public void execute_validEvent_success() {
+ User newUser = new UserBuilder(JANE).build();
+ model.setUser(newUser);
+ SetReminderCommand setReminderCommand = new SetReminderCommand("CS2103 Meeting");
+ String expectedMessage = SetReminderCommand.MESSAGE_SET_REMINDER_SUCCESS + "CS2103 Meeting";
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()),
+ new UserPrefs(), new UserData());
+ expectedModel.setUser(newUser);
+
+ assertCommandSuccess(setReminderCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_noSuchEvent_failure() {
+ User newUser = new UserBuilder().build();
+ model.setUser(newUser);
+ SetReminderCommand setReminderCommand = new SetReminderCommand("CS2101 Meeting");
+ String expectedMessage = SetReminderCommand.MESSAGE_NO_EVENT;
+
+ assertCommandFailure(setReminderCommand, model, expectedMessage);
+ }
+
+ @Test
+ public void equals() {
+ model.setUser(new UserBuilder().build());
+ final SetReminderCommand standardCommand = new SetReminderCommand("CS2103 Meeting");
+
+ // same object -> returns true
+ assertEquals(standardCommand, standardCommand);
+
+ // null -> returns false
+ assertNotEquals(null, standardCommand);
+
+ // different types -> returns false
+ assertNotEquals(standardCommand, new ClearCommand());
+
+ // different descriptor -> returns false
+ assertNotEquals(standardCommand, new SetReminderCommand("Walk Dog"));
+ }
+
+ @Test
+ public void toStringTest() {
+ SetReminderCommand setReminderCommand = new SetReminderCommand("CS2103 Meeting");
+ String expected = SetReminderCommand.class.getCanonicalName() + "{" + "Set Reminder for="
+ + "CS2103 Meeting" + "}";
+ assertEquals(expected, setReminderCommand.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/edit/EditCommandTest.java
similarity index 53%
rename from src/test/java/seedu/address/logic/commands/EditCommandTest.java
rename to src/test/java/seedu/address/logic/commands/edit/EditCommandTest.java
index 469dd97daa7..684dec53296 100644
--- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/edit/EditCommandTest.java
@@ -1,10 +1,12 @@
-package seedu.address.logic.commands;
+package seedu.address.logic.commands.edit;
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 static seedu.address.logic.commands.CommandTestUtil.DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
@@ -19,12 +21,15 @@
import seedu.address.commons.core.index.Index;
import seedu.address.logic.Messages;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
+import seedu.address.logic.commands.ClearCommand;
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
-import seedu.address.model.UserPrefs;
+import seedu.address.model.person.Email;
import seedu.address.model.person.Person;
+import seedu.address.model.person.Phone;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
import seedu.address.testutil.EditPersonDescriptorBuilder;
import seedu.address.testutil.PersonBuilder;
@@ -33,18 +38,28 @@
*/
public class EditCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
@Test
public void execute_allFieldsSpecifiedUnfilteredList_success() {
- Person editedPerson = new PersonBuilder().build();
- EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build();
- EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor);
+ Index indexLastPerson = Index.fromOneBased(model.getFilteredPersonList().size());
+ Person lastPerson = model.getFilteredPersonList().get(indexLastPerson.getZeroBased());
+
+ PersonBuilder personInList = new PersonBuilder(lastPerson);
+ Person editedPerson = personInList.withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB)
+ .withTags(VALID_TAG_HUSBAND).withBirthday("1985-05-02").withAddress(VALID_ADDRESS_BOB)
+ .withEmail(VALID_EMAIL_BOB).build();
+
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB)
+ .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).withBirthday("1985-05-02")
+ .withAddress(VALID_ADDRESS_BOB).withEmail(VALID_EMAIL_BOB).build();
+ EditCommand editCommand = new EditCommand(indexLastPerson, descriptor);
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
- expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()),
+ new UserPrefs(), new UserData());
+ expectedModel.setPerson(lastPerson, editedPerson);
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
}
@@ -64,7 +79,8 @@ public void execute_someFieldsSpecifiedUnfilteredList_success() {
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()),
+ new UserPrefs(), new UserData());
expectedModel.setPerson(lastPerson, editedPerson);
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
@@ -77,7 +93,8 @@ public void execute_noFieldSpecifiedUnfilteredList_success() {
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()),
+ new UserPrefs(), new UserData());
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
}
@@ -93,7 +110,8 @@ public void execute_filteredList_success() {
String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()),
+ new UserPrefs(), new UserData());
expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
@@ -120,6 +138,15 @@ public void execute_duplicatePersonFilteredList_failure() {
assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON);
}
+ @Test
+ public void execute_sameAsUser_failure() {
+ Person user = model.getUser();
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(user).build();
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor);
+
+ assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON);
+ }
+
@Test
public void execute_invalidPersonIndexUnfilteredList_failure() {
Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
@@ -146,6 +173,76 @@ public void execute_invalidPersonIndexFilteredList_failure() {
assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
+ @Test
+ public void execute_duplicatePhoneUnfilteredList_failure() {
+ Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ Phone duplicatePhone = model.getFilteredPersonList().get(INDEX_SECOND_PERSON.getZeroBased()).getPhone();
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstPerson).withPhone(duplicatePhone)
+ .build();
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor);
+
+ assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PHONE);
+ }
+
+ @Test
+ public void execute_duplicatePhoneFilteredList_failure() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+ Phone duplicatePhone = model.getAddressBook().getPersonList()
+ .get(INDEX_SECOND_PERSON.getZeroBased()).getPhone();
+ Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstPerson).withPhone(duplicatePhone)
+ .build();
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor);
+
+ assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PHONE);
+ }
+
+ @Test
+ public void execute_samePhoneAsUser_failure() {
+ Phone userPhone = model.getUser().getPhone();
+ Person personToEdit = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(personToEdit)
+ .withPhone(userPhone).build();
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor);
+
+ assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PHONE);
+ }
+
+ @Test
+ public void execute_duplicateEmailUnfilteredList_failure() {
+ Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ Email duplicateEmail = model.getFilteredPersonList().get(INDEX_SECOND_PERSON.getZeroBased()).getEmail();
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstPerson).withEmail(duplicateEmail)
+ .build();
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor);
+
+ assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_EMAIL);
+ }
+
+ @Test
+ public void execute_duplicateEmailFilteredList_failure() {
+ Email duplicateEmail = model.getFilteredPersonList().get(INDEX_SECOND_PERSON.getZeroBased()).getEmail();
+
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+ Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstPerson).withEmail(duplicateEmail)
+ .build();
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor);
+
+ assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_EMAIL);
+ }
+
+ @Test
+ public void execute_sameEmailAsUser_failure() {
+ Email userEmail = model.getUser().getEmail();
+ Person personToEdit = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(personToEdit)
+ .withEmail(userEmail).build();
+ EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor);
+
+ assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_EMAIL);
+ }
+
@Test
public void equals() {
final EditCommand standardCommand = new EditCommand(INDEX_FIRST_PERSON, DESC_AMY);
@@ -153,26 +250,26 @@ public void equals() {
// same values -> returns true
EditPersonDescriptor copyDescriptor = new EditPersonDescriptor(DESC_AMY);
EditCommand commandWithSameValues = new EditCommand(INDEX_FIRST_PERSON, copyDescriptor);
- assertTrue(standardCommand.equals(commandWithSameValues));
+ assertEquals(standardCommand, commandWithSameValues);
// same object -> returns true
- assertTrue(standardCommand.equals(standardCommand));
+ assertEquals(standardCommand, standardCommand);
// null -> returns false
- assertFalse(standardCommand.equals(null));
+ assertNotEquals(null, standardCommand);
// different types -> returns false
- assertFalse(standardCommand.equals(new ClearCommand()));
+ assertNotEquals(standardCommand, new ClearCommand());
// different index -> returns false
- assertFalse(standardCommand.equals(new EditCommand(INDEX_SECOND_PERSON, DESC_AMY)));
+ assertNotEquals(standardCommand, new EditCommand(INDEX_SECOND_PERSON, DESC_AMY));
// different descriptor -> returns false
- assertFalse(standardCommand.equals(new EditCommand(INDEX_FIRST_PERSON, DESC_BOB)));
+ assertNotEquals(standardCommand, new EditCommand(INDEX_FIRST_PERSON, DESC_BOB));
}
@Test
- public void toStringMethod() {
+ public void toStringMethod_nullEditPersonDescriptor() {
Index index = Index.fromOneBased(1);
EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
EditCommand editCommand = new EditCommand(index, editPersonDescriptor);
@@ -181,4 +278,14 @@ public void toStringMethod() {
assertEquals(expected, editCommand.toString());
}
+ @Test
+ public void toStringMethod_filledEditPersonDescriptor() {
+ Index index = Index.fromOneBased(1);
+ EditPersonDescriptor editPersonDescriptor = DESC_BOB;
+ EditCommand editCommand = new EditCommand(index, editPersonDescriptor);
+ String expected = EditCommand.class.getCanonicalName() + "{index=" + index + ", editPersonDescriptor="
+ + editPersonDescriptor + "}";
+ assertEquals(expected, editCommand.toString());
+ }
+
}
diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/commands/edit/EditPersonDescriptorTest.java
similarity index 50%
rename from src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
rename to src/test/java/seedu/address/logic/commands/edit/EditPersonDescriptorTest.java
index b17c1f3d5c2..78d4252f189 100644
--- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
+++ b/src/test/java/seedu/address/logic/commands/edit/EditPersonDescriptorTest.java
@@ -1,8 +1,7 @@
-package seedu.address.logic.commands;
+package seedu.address.logic.commands.edit;
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 org.junit.jupiter.api.Assertions.assertNotEquals;
import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
@@ -10,10 +9,10 @@
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.testutil.TypicalSchedule.NORMAL_SCHEDULE;
import org.junit.jupiter.api.Test;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.testutil.EditPersonDescriptorBuilder;
public class EditPersonDescriptorTest {
@@ -22,50 +21,79 @@ public class EditPersonDescriptorTest {
public void equals() {
// same values -> returns true
EditPersonDescriptor descriptorWithSameValues = new EditPersonDescriptor(DESC_AMY);
- assertTrue(DESC_AMY.equals(descriptorWithSameValues));
+ assertEquals(DESC_AMY, descriptorWithSameValues);
// same object -> returns true
- assertTrue(DESC_AMY.equals(DESC_AMY));
-
- // null -> returns false
- assertFalse(DESC_AMY.equals(null));
+ assertEquals(DESC_AMY, DESC_AMY);
// different types -> returns false
- assertFalse(DESC_AMY.equals(5));
+ assertNotEquals(VALID_ADDRESS_BOB, DESC_AMY);
// different values -> returns false
- assertFalse(DESC_AMY.equals(DESC_BOB));
+ assertNotEquals(DESC_AMY, DESC_BOB);
// different name -> returns false
EditPersonDescriptor editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withName(VALID_NAME_BOB).build();
- assertFalse(DESC_AMY.equals(editedAmy));
+ assertNotEquals(DESC_AMY, editedAmy);
// different phone -> returns false
editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withPhone(VALID_PHONE_BOB).build();
- assertFalse(DESC_AMY.equals(editedAmy));
+ assertNotEquals(DESC_AMY, editedAmy);
// different email -> returns false
editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withEmail(VALID_EMAIL_BOB).build();
- assertFalse(DESC_AMY.equals(editedAmy));
+ assertNotEquals(DESC_AMY, editedAmy);
// different address -> returns false
editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withAddress(VALID_ADDRESS_BOB).build();
- assertFalse(DESC_AMY.equals(editedAmy));
+ assertNotEquals(DESC_AMY, editedAmy);
+
+ // different birthday -> return false
+ editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withBirthday("1990-01-01").build();
+ assertNotEquals(DESC_AMY, editedAmy);
+
+ // different free times -> returns false
+ editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withSchedule(NORMAL_SCHEDULE).build();
+ assertNotEquals(DESC_AMY, editedAmy);
// different tags -> returns false
editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withTags(VALID_TAG_HUSBAND).build();
- assertFalse(DESC_AMY.equals(editedAmy));
+ assertNotEquals(DESC_AMY, editedAmy);
}
@Test
- public void toStringMethod() {
+ public void equals_null() {
+ EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
+ assertEquals(editPersonDescriptor, editPersonDescriptor);
+ assertNotEquals(editPersonDescriptor, null);
+ }
+
+ @Test
+ public void toStringMethod_null() {
EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
String expected = EditPersonDescriptor.class.getCanonicalName() + "{name="
+ editPersonDescriptor.getName().orElse(null) + ", phone="
+ editPersonDescriptor.getPhone().orElse(null) + ", email="
+ editPersonDescriptor.getEmail().orElse(null) + ", address="
- + editPersonDescriptor.getAddress().orElse(null) + ", tags="
+ + editPersonDescriptor.getBirthday().orElse(null) + ", birthday="
+ + editPersonDescriptor.getAddress().orElse(null) + ", free times="
+ + editPersonDescriptor.getSchedule().orElse(null) + ", tags="
+ editPersonDescriptor.getTags().orElse(null) + "}";
assertEquals(expected, editPersonDescriptor.toString());
}
+
+ @Test
+ public void toStringMethod_filled() {
+ EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(DESC_BOB);
+ String expected = EditPersonDescriptor.class.getCanonicalName() + "{name="
+ + editPersonDescriptor.getName().orElse(null) + ", phone="
+ + editPersonDescriptor.getPhone().orElse(null) + ", email="
+ + editPersonDescriptor.getEmail().orElse(null) + ", address="
+ + editPersonDescriptor.getAddress().orElse(null) + ", birthday="
+ + editPersonDescriptor.getBirthday().orElse(null) + ", free times="
+ + editPersonDescriptor.getSchedule().orElse(null) + ", tags="
+ + editPersonDescriptor.getTags().orElse(null) + "}";
+ assertEquals(expected, editPersonDescriptor.toString());
+ }
+
}
diff --git a/src/test/java/seedu/address/logic/commands/edit/EditUserCommandTest.java b/src/test/java/seedu/address/logic/commands/edit/EditUserCommandTest.java
new file mode 100644
index 00000000000..23384f5730a
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/edit/EditUserCommandTest.java
@@ -0,0 +1,161 @@
+package seedu.address.logic.commands.edit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static seedu.address.logic.commands.CommandTestUtil.DESC_USER_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.DESC_USER_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+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.TypicalPersons.getTypicalAddressBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.ClearCommand;
+import seedu.address.model.AddressBook;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Phone;
+import seedu.address.model.user.User;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
+import seedu.address.testutil.EditUserDescriptorBuilder;
+import seedu.address.testutil.UserBuilder;
+
+public class EditUserCommandTest {
+ private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs(), new UserData());
+
+ @Test
+ public void execute_allFieldsSpecified_success() {
+ User newUser = new UserBuilder().build();
+ EditUserDescriptor descriptor = new EditUserDescriptorBuilder(newUser).build();
+ EditUserCommand editUserCommand = new EditUserCommand(descriptor);
+
+ String expectedMessage = String.format(EditUserCommand.MESSAGE_EDIT_USER_SUCCESS, Messages.format(newUser));
+
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()),
+ new UserPrefs(), new UserData());
+ expectedModel.setUser(newUser);
+
+ assertCommandSuccess(editUserCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_someFieldsSpecified_success() {
+ User newUser = new UserBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB)
+ .withTags(VALID_TAG_HUSBAND).build();
+ EditUserDescriptor descriptor = new EditUserDescriptorBuilder(newUser).withName(VALID_NAME_BOB)
+ .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build();
+ EditUserCommand editUserCommand = new EditUserCommand(descriptor);
+
+ String expectedMessage = String.format(EditUserCommand.MESSAGE_EDIT_USER_SUCCESS, Messages.format(newUser));
+
+ Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()),
+ new UserPrefs(), new UserData());
+ expectedModel.setUser(newUser);
+
+ assertCommandSuccess(editUserCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_duplicateUser_failure() {
+ User user = model.getUser();
+ EditUserDescriptor descriptor = new EditUserDescriptorBuilder(user).build();
+ EditUserCommand editUserCommand = new EditUserCommand(descriptor);
+
+ assertCommandFailure(editUserCommand, model, EditUserCommand.MESSAGE_DUPLICATE_USER);
+ }
+
+ @Test
+ public void execute_duplicatePhoneUnfilteredList_failure() {
+ User user = model.getUser();
+ Phone duplicatePhone = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()).getPhone();
+ EditUserDescriptor descriptor = new EditUserDescriptorBuilder(user).withPhone(duplicatePhone).build();
+ EditUserCommand editUserCommand = new EditUserCommand(descriptor);
+
+ assertCommandFailure(editUserCommand, model, EditUserCommand.MESSAGE_DUPLICATE_PHONE);
+ }
+
+ @Test
+ public void execute_duplicatePhoneFilteredList_failure() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+ User user = model.getUser();
+ Phone duplicatePhone = model.getAddressBook().getPersonList()
+ .get(INDEX_SECOND_PERSON.getZeroBased()).getPhone();
+ EditUserDescriptor descriptor = new EditUserDescriptorBuilder(user).withPhone(duplicatePhone).build();
+ EditUserCommand editUserCommand = new EditUserCommand(descriptor);
+
+ assertCommandFailure(editUserCommand, model, EditUserCommand.MESSAGE_DUPLICATE_PHONE);
+ }
+
+ @Test
+ public void execute_duplicateEmailUnfilteredList_failure() {
+ User user = model.getUser();
+ Email duplicateEmail = model.getAddressBook().getPersonList()
+ .get(INDEX_FIRST_PERSON.getZeroBased()).getEmail();
+ EditUserDescriptor descriptor = new EditUserDescriptorBuilder(user).withEmail(duplicateEmail).build();
+ EditUserCommand editUserCommand = new EditUserCommand(descriptor);
+
+ assertCommandFailure(editUserCommand, model, EditUserCommand.MESSAGE_DUPLICATE_EMAIL);
+ }
+
+ @Test
+ public void execute_duplicateEmailFilteredList_failure() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+ User user = model.getUser();
+ Email duplicateEmail = model.getAddressBook().getPersonList()
+ .get(INDEX_SECOND_PERSON.getZeroBased()).getEmail();
+ EditUserDescriptor descriptor = new EditUserDescriptorBuilder(user).withEmail(duplicateEmail).build();
+ EditUserCommand editUserCommand = new EditUserCommand(descriptor);
+
+ assertCommandFailure(editUserCommand, model, EditUserCommand.MESSAGE_DUPLICATE_EMAIL);
+ }
+
+ @Test
+ public void equals() {
+ final EditUserCommand standardCommand = new EditUserCommand(DESC_USER_AMY);
+
+ // same values -> returns true
+ EditUserDescriptor copyDescriptor = new EditUserDescriptor(DESC_USER_AMY);
+ EditUserCommand commandWithSameValues = new EditUserCommand(copyDescriptor);
+ assertEquals(standardCommand, commandWithSameValues);
+
+ // same object -> returns true
+ assertEquals(standardCommand, standardCommand);
+
+ // null -> returns false
+ assertNotEquals(null, standardCommand);
+
+ // different types -> returns false
+ assertNotEquals(standardCommand, new ClearCommand());
+
+ // different descriptor -> returns false
+ assertNotEquals(standardCommand, new EditUserCommand(DESC_USER_BOB));
+ }
+
+ @Test
+ public void toStringMethod_nullEditUserDescriptor() {
+ EditUserDescriptor editUserDescriptor = new EditUserDescriptor();
+ EditUserCommand editUserCommand = new EditUserCommand(editUserDescriptor);
+ String expected = EditUserCommand.class.getCanonicalName() + "{" + "editUserDescriptor="
+ + editUserDescriptor + "}";
+ assertEquals(expected, editUserCommand.toString());
+ }
+
+ @Test
+ public void toStringMethod_filledEditPersonDescriptor() {
+ EditUserDescriptor editUserDescriptor = DESC_USER_AMY;
+ EditUserCommand editUserCommand = new EditUserCommand(editUserDescriptor);
+ String expected = EditUserCommand.class.getCanonicalName() + "{" + "editUserDescriptor="
+ + editUserDescriptor + "}";
+ assertEquals(expected, editUserCommand.toString());
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/edit/EditUserDescriptorTest.java b/src/test/java/seedu/address/logic/commands/edit/EditUserDescriptorTest.java
new file mode 100644
index 00000000000..a110a016d00
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/edit/EditUserDescriptorTest.java
@@ -0,0 +1,18 @@
+package seedu.address.logic.commands.edit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class EditUserDescriptorTest {
+
+ @Test
+ public void equals() {
+ EditUserDescriptor editUserDescriptor = new EditUserDescriptor();
+ assertEquals(editUserDescriptor, editUserDescriptor);
+
+ assertNotEquals(editUserDescriptor, null);
+
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
index 5bc11d3cdaa..985aa4a504a 100644
--- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
@@ -3,9 +3,12 @@
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.BIRTHDAY_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.BIRTHDAY_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_BIRTHDAY_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC;
import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC;
@@ -14,30 +17,25 @@
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY;
import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE;
import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND;
import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND;
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_BIRTHDAY_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
import static seedu.address.testutil.TypicalPersons.AMY;
-import static seedu.address.testutil.TypicalPersons.BOB;
import org.junit.jupiter.api.Test;
-import seedu.address.logic.Messages;
import seedu.address.logic.commands.AddCommand;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Birthday;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
@@ -50,91 +48,104 @@ public class AddCommandParserTest {
@Test
public void parse_allFieldsPresent_success() {
- Person expectedPerson = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND).build();
+ Person expectedPerson = new PersonBuilder(AMY).withTags(VALID_TAG_FRIEND).build();
// whitespace only preamble
- assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson));
+ assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY
+ + ADDRESS_DESC_AMY + BIRTHDAY_DESC_AMY + TAG_DESC_FRIEND, new AddCommand(expectedPerson));
// multiple tags - all accepted
- Person expectedPersonMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND)
+ Person expectedPersonMultipleTags = new PersonBuilder(AMY).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND)
.build();
assertParseSuccess(parser,
- NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
+ NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + BIRTHDAY_DESC_AMY
+ + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
new AddCommand(expectedPersonMultipleTags));
}
@Test
public void parse_repeatedNonTagValue_failure() {
String validExpectedPersonString = NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_FRIEND;
+ + ADDRESS_DESC_BOB + BIRTHDAY_DESC_BOB + TAG_DESC_FRIEND;
// multiple names
assertParseFailure(parser, NAME_DESC_AMY + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME));
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: n/ ");
// multiple phones
assertParseFailure(parser, PHONE_DESC_AMY + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: p/ ");
// multiple emails
assertParseFailure(parser, EMAIL_DESC_AMY + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_EMAIL));
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: e/ ");
// multiple addresses
assertParseFailure(parser, ADDRESS_DESC_AMY + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS));
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: a/ ");
+
+ // multiple birthdays
+ assertParseFailure(parser, BIRTHDAY_DESC_AMY + validExpectedPersonString,
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: b/ ");
// multiple fields repeated
assertParseFailure(parser,
validExpectedPersonString + PHONE_DESC_AMY + EMAIL_DESC_AMY + NAME_DESC_AMY + ADDRESS_DESC_AMY
- + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME, PREFIX_ADDRESS, PREFIX_EMAIL, PREFIX_PHONE));
+ + BIRTHDAY_DESC_AMY + validExpectedPersonString,
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: n/ p/ e/ a/ b/ ");
// invalid value followed by valid value
// invalid name
assertParseFailure(parser, INVALID_NAME_DESC + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME));
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: n/ ");
// invalid email
assertParseFailure(parser, INVALID_EMAIL_DESC + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_EMAIL));
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: e/ ");
// invalid phone
assertParseFailure(parser, INVALID_PHONE_DESC + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: p/ ");
// invalid address
assertParseFailure(parser, INVALID_ADDRESS_DESC + validExpectedPersonString,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS));
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: a/ ");
+
+ // invalid birthday
+ assertParseFailure(parser, INVALID_BIRTHDAY_DESC + validExpectedPersonString,
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: b/ ");
// valid value followed by invalid value
// invalid name
assertParseFailure(parser, validExpectedPersonString + INVALID_NAME_DESC,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME));
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: n/ ");
// invalid email
assertParseFailure(parser, validExpectedPersonString + INVALID_EMAIL_DESC,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_EMAIL));
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: e/ ");
// invalid phone
assertParseFailure(parser, validExpectedPersonString + INVALID_PHONE_DESC,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: p/ ");
// invalid address
assertParseFailure(parser, validExpectedPersonString + INVALID_ADDRESS_DESC,
- Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS));
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: a/ ");
+
+ // invalid birthday
+ assertParseFailure(parser, validExpectedPersonString + INVALID_BIRTHDAY_DESC,
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: b/ ");
}
@Test
public void parse_optionalFieldsMissing_success() {
// zero tags
Person expectedPerson = new PersonBuilder(AMY).withTags().build();
- assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY,
- new AddCommand(expectedPerson));
+ assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY
+ + BIRTHDAY_DESC_AMY, new AddCommand(expectedPerson));
}
@Test
@@ -142,55 +153,45 @@ public void parse_compulsoryFieldMissing_failure() {
String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE);
// missing name prefix
- assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB,
- expectedMessage);
-
- // missing phone prefix
- assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB,
- expectedMessage);
-
- // missing email prefix
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB,
- expectedMessage);
-
- // missing address prefix
- assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB,
- expectedMessage);
+ assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ + BIRTHDAY_DESC_BOB, "Missing prefix(es) for n/ !\n" + "Message Usage:\n"
+ + AddCommand.MESSAGE_USAGE);
// all prefixes missing
- assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB,
- expectedMessage);
+ assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB
+ + VALID_BIRTHDAY_BOB, "Missing prefix(es) for n/ p/ e/ a/ b/ !\n" + "Message Usage:\n"
+ + AddCommand.MESSAGE_USAGE);
}
@Test
public void parse_invalidValue_failure() {
// invalid name
assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_CONSTRAINTS);
+ + BIRTHDAY_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_CONSTRAINTS);
// invalid phone
assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS);
+ + BIRTHDAY_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS);
// invalid email
assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS);
+ + BIRTHDAY_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS);
// invalid address
assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Address.MESSAGE_CONSTRAINTS);
+ + BIRTHDAY_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Address.MESSAGE_CONSTRAINTS);
+
+ // invalid birthday
+ assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
+ + INVALID_BIRTHDAY_DESC + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Birthday.MESSAGE_CONSTRAINTS);
// invalid tag
assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
- + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CONSTRAINTS);
+ + BIRTHDAY_DESC_BOB + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CONSTRAINTS);
// two invalid values, only first invalid value reported
- assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC,
- Name.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB
+ + INVALID_ADDRESS_DESC + BIRTHDAY_DESC_BOB, Name.MESSAGE_CONSTRAINTS);
- // non-empty preamble
- assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
}
}
diff --git a/src/test/java/seedu/address/logic/parser/AddEventCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddEventCommandParserTest.java
new file mode 100644
index 00000000000..7d587b43e91
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/AddEventCommandParserTest.java
@@ -0,0 +1,80 @@
+package seedu.address.logic.parser;
+
+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.commons.core.index.Index;
+import seedu.address.logic.commands.AddEventCommand;
+
+public class AddEventCommandParserTest {
+ private final AddEventCommandParser parser = new AddEventCommandParser();
+
+ @Test
+ public void parse_allFieldsPresent_success() {
+
+ // dated event in user
+ assertParseSuccess(parser, "user en/CS2103 Meeting h/2023-10-10 1030 1130 r/y",
+ new AddEventCommand("CS2103 MEETING", "2023-10-10 1030 1130", "y"));
+
+ // dated event in friend
+ assertParseSuccess(parser, "1 en/CS2103 Meeting h/2023-10-10 1030 1130 r/y",
+ new AddEventCommand("CS2103 MEETING", Index.fromOneBased(1),
+ "2023-10-10 1030 1130", "y"));
+
+ }
+
+ @Test
+ public void parse_repeatedValue_failure() {
+ String validCommand = "user en/CS2103 Meeting h/2023-10-10 1030 1130 r/y";
+
+ // multiple event name
+ assertParseFailure(parser, validCommand + " en/CS2103 Meeting",
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: en/ ");
+
+ // multiple event schedule
+ assertParseFailure(parser, validCommand + " h/2023-10-10 1030 1130",
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: h/ ");
+
+ // multiple event reminder
+ assertParseFailure(parser, validCommand + " r/y",
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: r/ ");
+
+ // multiple fields repeated
+ assertParseFailure(parser, validCommand + " en/CS2103 Meeting" + " r/y",
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: en/ r/ ");
+
+ }
+
+ @Test
+ public void parse_fieldMissing_failure() {
+
+ // missing event name prefix
+ assertParseFailure(parser, "user h/2023-10-10 1030 1130 r/y",
+ "Missing prefix(es) for en/ !\n" + "Message Usage:\n" + AddEventCommand.MESSAGE_USAGE);
+
+ // missing event schedule prefix
+ assertParseFailure(parser, "user en/CS2103 Meeting r/y",
+ "Missing prefix(es) for h/ !\n" + "Message Usage:\n" + AddEventCommand.MESSAGE_USAGE);
+
+ // missing event reminder prefix
+ assertParseFailure(parser, "user en/CS2103 Meeting h/2023-10-10 1030 1130",
+ "Missing prefix(es) for r/ !\n" + "Message Usage:\n" + AddEventCommand.MESSAGE_USAGE);
+
+ // missing multiple prefixes
+ assertParseFailure(parser, "user en/CS2103 Meeting",
+ "Missing prefix(es) for h/ r/ !\n" + "Message Usage:\n" + AddEventCommand.MESSAGE_USAGE);
+
+ // all prefixes missing
+ assertParseFailure(parser, "user CS2103 Meeting 2023-10-10 1030 1130 y",
+ "Missing prefix(es) for en/ h/ r/ !\n" + "Message Usage:\n"
+ + AddEventCommand.MESSAGE_USAGE);
+
+ // wrong index
+ assertParseFailure(parser, "wrong en/CS2103 Meeting h/2023-10-10 1030 1130 r/y",
+ "Invalid index!\n" + "Index can only be 'user' or a positive integer! \n");
+
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddScheduleCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddScheduleCommandParserTest.java
new file mode 100644
index 00000000000..d1f8dbcc681
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/AddScheduleCommandParserTest.java
@@ -0,0 +1,91 @@
+package seedu.address.logic.parser;
+
+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.commons.core.index.Index;
+import seedu.address.logic.commands.AddScheduleCommand;
+
+public class AddScheduleCommandParserTest {
+ private AddScheduleCommandParser parser = new AddScheduleCommandParser();
+
+ @Test
+ public void parse_allFieldsPresent_success() {
+
+ // cca in user
+ assertParseSuccess(parser, "user type/cca en/Basketball h/Monday 1030 1130",
+ new AddScheduleCommand("BASKETBALL", "cca", "Monday 1030 1130"));
+
+ // cca in friend
+ assertParseSuccess(parser, "1 type/cca en/Basketball h/Monday 1030 1130",
+ new AddScheduleCommand("BASKETBALL", "cca", Index.fromOneBased(1),
+ "Monday 1030 1130"));
+
+ // module in user
+ assertParseSuccess(parser, "user type/module en/CS2103 h/Monday 1030 1130",
+ new AddScheduleCommand("CS2103", "module", "Monday 1030 1130"));
+
+ // module in friend
+ assertParseSuccess(parser, "1 type/module en/CS2103 h/Monday 1030 1130",
+ new AddScheduleCommand("CS2103", "module", Index.fromOneBased(1),
+ "Monday 1030 1130"));
+ }
+
+ @Test
+ public void parse_repeatedValue_failure() {
+ String validCommand = "user type/cca en/Basketball h/Monday 1030 1130";
+
+ // multiple event type
+ assertParseFailure(parser, validCommand + " type/cca",
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: type/ ");
+
+ // multiple event name
+ assertParseFailure(parser, validCommand + " en/CS2103",
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: en/ ");
+
+ // multiple event schedule
+ assertParseFailure(parser, validCommand + " h/Monday 1030 1130",
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: h/ ");
+
+ // multiple fields repeated
+ assertParseFailure(parser, validCommand + " type/cca" + " en/Basketball",
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: en/ type/ ");
+
+ }
+
+ @Test
+ public void parse_fieldMissing_failure() {
+
+ // missing schedule name prefix
+ assertParseFailure(parser, "user type/cca h/Monday 1030 1130",
+ "Missing prefix(es) for en/ !\n" + "Message Usage:\n" + AddScheduleCommand.MESSAGE_USAGE);
+
+ // missing schedule type prefix
+ assertParseFailure(parser, "user en/Basketball h/Monday 1030 1130",
+ "Missing prefix(es) for type/ !\n" + "Message Usage:\n"
+ + AddScheduleCommand.MESSAGE_USAGE);
+
+ // missing schedule prefix
+ assertParseFailure(parser, "user type/cca en/Basketball",
+ "Missing prefix(es) for h/ !\n" + "Message Usage:\n"
+ + AddScheduleCommand.MESSAGE_USAGE);
+
+ // missing multiple prefix
+ assertParseFailure(parser, "user type/cca",
+ "Missing prefix(es) for en/ h/ !\n" + "Message Usage:\n"
+ + AddScheduleCommand.MESSAGE_USAGE);
+
+ // all prefixes missing
+ assertParseFailure(parser, "user cca Basketball Monday 1030 1130",
+ "Missing prefix(es) for en/ type/ h/ !\n" + "Message Usage:\n"
+ + AddScheduleCommand.MESSAGE_USAGE);
+
+ // wrong index
+ assertParseFailure(parser, "wrong type/cca en/CS2103 Meeting h/2023-10-10 1030 1130 r/y",
+ String.format("Invalid index!\n"
+ + "Index can only be 'user' or a positive integer! \n"));
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
index 5a1ab3dbc0c..668438dfef0 100644
--- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
@@ -6,6 +6,7 @@
import static seedu.address.logic.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.TypicalUsers.JOHN;
import java.util.Arrays;
import java.util.List;
@@ -13,21 +14,34 @@
import org.junit.jupiter.api.Test;
+import seedu.address.commons.core.index.Index;
import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.AddEventCommand;
+import seedu.address.logic.commands.AddScheduleCommand;
import seedu.address.logic.commands.ClearCommand;
+import seedu.address.logic.commands.CommonFreetimeCommand;
import seedu.address.logic.commands.DeleteCommand;
-import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
import seedu.address.logic.commands.ExitCommand;
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.RemoveEventCommand;
+import seedu.address.logic.commands.RemoveReminderCommand;
+import seedu.address.logic.commands.RemoveScheduleCommand;
+import seedu.address.logic.commands.SetReminderCommand;
+import seedu.address.logic.commands.edit.EditCommand;
+import seedu.address.logic.commands.edit.EditPersonDescriptor;
+import seedu.address.logic.commands.edit.EditUserCommand;
+import seedu.address.logic.commands.edit.EditUserDescriptor;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.person.NameContainsKeywordsPredicate;
import seedu.address.model.person.Person;
+import seedu.address.model.user.User;
import seedu.address.testutil.EditPersonDescriptorBuilder;
+import seedu.address.testutil.EditUserDescriptorBuilder;
import seedu.address.testutil.PersonBuilder;
import seedu.address.testutil.PersonUtil;
+import seedu.address.testutil.UserBuilder;
public class AddressBookParserTest {
@@ -62,6 +76,75 @@ public void parseCommand_edit() throws Exception {
assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command);
}
+ @Test
+ public void parseCommand_editUser() throws Exception {
+ User user = new UserBuilder(JOHN).build();
+ EditUserDescriptor descriptor = new EditUserDescriptorBuilder(user).build();
+ EditUserCommand command = (EditUserCommand) parser.parseCommand(EditUserCommand.COMMAND_WORD + " "
+ + PersonUtil.getEditPersonDescriptorDetails(descriptor));
+ assertEquals(new EditUserCommand(descriptor), command);
+ }
+
+ @Test
+ public void parseCommand_commonFreetime_validArgs() throws Exception {
+ Person person = new PersonBuilder().build();
+ CommonFreetimeCommand command = (CommonFreetimeCommand) parser.parseCommand(
+ CommonFreetimeCommand.COMMAND_WORD + " " + "1");
+ assertEquals(new CommonFreetimeCommand(Index.fromOneBased(1)), command);
+ }
+
+ @Test
+ public void parseCommand_commonFreetime_all() throws Exception {
+ CommonFreetimeCommand command = (CommonFreetimeCommand) parser.parseCommand(
+ CommonFreetimeCommand.COMMAND_WORD);
+ assertEquals(new CommonFreetimeCommand(), command);
+ }
+
+ @Test
+ public void parseCommand_setReminder() throws Exception {
+ SetReminderCommand command = (SetReminderCommand) parser.parseCommand(
+ SetReminderCommand.COMMAND_WORD + " CS2103 Meeting");
+ assertEquals(new SetReminderCommand("CS2103 Meeting"), command);
+ }
+
+ @Test
+ public void parseCommand_removeReminder() throws Exception {
+ RemoveReminderCommand command = (RemoveReminderCommand) parser.parseCommand(
+ RemoveReminderCommand.COMMAND_WORD + " CS2103 Meeting");
+ assertEquals(new RemoveReminderCommand("CS2103 Meeting"), command);
+ }
+
+ @Test
+ public void parseCommand_addEvent() throws Exception {
+ AddEventCommand command = (AddEventCommand) parser.parseCommand(
+ AddEventCommand.COMMAND_WORD + " user en/CS2103 Meeting h/2023-10-10 1030 1130 r/y");
+ assertEquals(new AddEventCommand("CS2103 MEETING",
+ "2023-10-10 1030 1130", "y"), command);
+ }
+
+ @Test
+ public void parseCommand_addSchedule() throws Exception {
+ AddScheduleCommand command = (AddScheduleCommand) parser.parseCommand(
+ AddScheduleCommand.COMMAND_WORD + " user type/cca en/Basketball h/Monday 1030 1130");
+ assertEquals(new AddScheduleCommand("BASKETBALL", "cca", "Monday 1030 1130"),
+ command);
+ }
+
+ @Test
+ public void parseCommand_removeEvent() throws Exception {
+ RemoveEventCommand command = (RemoveEventCommand) parser.parseCommand(
+ RemoveEventCommand.COMMAND_WORD + " user en/CS2103 Meeting");
+ assertEquals(new RemoveEventCommand("CS2103 MEETING", null), command);
+ }
+
+ @Test
+ public void parseCommand_removeSchedule() throws Exception {
+ RemoveScheduleCommand command = (RemoveScheduleCommand) parser.parseCommand(
+ RemoveScheduleCommand.COMMAND_WORD + " user type/cca en/Basketball");
+ assertEquals(new RemoveScheduleCommand("BASKETBALL", "cca"), command);
+ }
+
+
@Test
public void parseCommand_exit() throws Exception {
assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD) instanceof ExitCommand);
@@ -91,7 +174,7 @@ public void parseCommand_list() throws Exception {
@Test
public void parseCommand_unrecognisedInput_throwsParseException() {
assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), ()
- -> parser.parseCommand(""));
+ -> parser.parseCommand(""));
}
@Test
diff --git a/src/test/java/seedu/address/logic/parser/CommonFreeTimeCommandParserTest.java b/src/test/java/seedu/address/logic/parser/CommonFreeTimeCommandParserTest.java
new file mode 100644
index 00000000000..98d35462223
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/CommonFreeTimeCommandParserTest.java
@@ -0,0 +1,19 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.CommonFreetimeCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class CommonFreeTimeCommandParserTest {
+ private CommonFreeTimeCommandParser parser = new CommonFreeTimeCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsCommonFreetimeCommand() throws ParseException {
+ assertParseSuccess(parser, "1",
+ new CommonFreetimeCommand(ParserUtil.parseIndex("1")));
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
index cc7175172d4..d97e3eeaa46 100644
--- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
@@ -36,8 +36,8 @@
import seedu.address.commons.core.index.Index;
import seedu.address.logic.Messages;
-import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
+import seedu.address.logic.commands.edit.EditCommand;
+import seedu.address.logic.commands.edit.EditPersonDescriptor;
import seedu.address.model.person.Address;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
@@ -99,8 +99,8 @@ public void parse_invalidValue_failure() {
assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS);
// multiple invalid values, but only the first invalid value is captured
- assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC + VALID_ADDRESS_AMY + VALID_PHONE_AMY,
- Name.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC
+ + VALID_ADDRESS_AMY + VALID_PHONE_AMY, Name.MESSAGE_CONSTRAINTS);
}
@Test
@@ -205,4 +205,5 @@ public void parse_resetTags_success() {
assertParseSuccess(parser, userInput, expectedCommand);
}
+
}
diff --git a/src/test/java/seedu/address/logic/parser/EditUserCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditUserCommandParserTest.java
new file mode 100644
index 00000000000..7ae5d2812b7
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/EditUserCommandParserTest.java
@@ -0,0 +1,189 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND;
+import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
+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 org.junit.jupiter.api.Test;
+
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.edit.EditUserCommand;
+import seedu.address.logic.commands.edit.EditUserDescriptor;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Phone;
+import seedu.address.model.tag.Tag;
+import seedu.address.testutil.EditUserDescriptorBuilder;
+
+public class EditUserCommandParserTest {
+
+ private static final String TAG_EMPTY = " " + PREFIX_TAG;
+
+ private static final String MESSAGE_INVALID_FORMAT =
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditUserCommand.MESSAGE_USAGE);
+
+ private EditUserCommandParser parser = new EditUserCommandParser();
+
+ @Test
+ public void parse_missingParts_failure() {
+ // no field specified
+ assertParseFailure(parser, "", EditUserCommand.MESSAGE_NOT_EDITED);
+ // wrong format
+ assertParseFailure(parser, "1", MESSAGE_INVALID_FORMAT);
+ assertParseFailure(parser, VALID_NAME_AMY, MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_invalidPreamble_failure() {
+ // invalid arguments being parsed as preamble
+ assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT);
+
+ // invalid prefix being parsed as preamble
+ assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test
+ public void parse_invalidValue_failure() {
+ assertParseFailure(parser, INVALID_NAME_DESC, Name.MESSAGE_CONSTRAINTS); // invalid name
+ assertParseFailure(parser, INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); // invalid phone
+ assertParseFailure(parser, INVALID_EMAIL_DESC, Email.MESSAGE_CONSTRAINTS); // invalid email
+ assertParseFailure(parser, INVALID_ADDRESS_DESC, Address.MESSAGE_CONSTRAINTS); // invalid address
+ assertParseFailure(parser, INVALID_TAG_DESC, Tag.MESSAGE_CONSTRAINTS); // invalid tag
+
+ // invalid phone followed by valid email
+ assertParseFailure(parser, INVALID_PHONE_DESC + EMAIL_DESC_AMY, Phone.MESSAGE_CONSTRAINTS);
+
+ // while parsing {@code PREFIX_TAG} alone will reset the tags of the {@code Person} being edited,
+ // parsing it together with a valid tag results in error
+ assertParseFailure(parser, TAG_DESC_FRIEND + TAG_DESC_HUSBAND + TAG_EMPTY, Tag.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS);
+
+ // multiple invalid values, but only the first invalid value is captured
+ assertParseFailure(parser, INVALID_NAME_DESC + INVALID_EMAIL_DESC
+ + VALID_ADDRESS_AMY + VALID_PHONE_AMY, Name.MESSAGE_CONSTRAINTS);
+ }
+ @Test
+ public void parse_allFieldsSpecified_success() {
+ String userInput = PHONE_DESC_BOB + TAG_DESC_HUSBAND
+ + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NAME_DESC_AMY + TAG_DESC_FRIEND;
+
+ EditUserDescriptor descriptor = new EditUserDescriptorBuilder().withName(VALID_NAME_AMY)
+ .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY)
+ .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
+ EditUserCommand expectedCommand = new EditUserCommand(descriptor);
+
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+
+ @Test
+ public void parse_someFieldsSpecified_success() {
+ String userInput = PHONE_DESC_BOB + EMAIL_DESC_AMY;
+
+ EditUserDescriptor descriptor = new EditUserDescriptorBuilder().withPhone(VALID_PHONE_BOB)
+ .withEmail(VALID_EMAIL_AMY).build();
+ EditUserCommand expectedCommand = new EditUserCommand(descriptor);
+
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+
+ @Test
+ public void parse_oneFieldSpecified_success() {
+ String userInput = NAME_DESC_AMY;
+ EditUserDescriptor descriptor = new EditUserDescriptorBuilder().withName(VALID_NAME_AMY).build();
+ EditUserCommand expectedCommand = new EditUserCommand(descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // phone
+ userInput = PHONE_DESC_AMY;
+ descriptor = new EditUserDescriptorBuilder().withPhone(VALID_PHONE_AMY).build();
+ expectedCommand = new EditUserCommand(descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // email
+ userInput = EMAIL_DESC_AMY;
+ descriptor = new EditUserDescriptorBuilder().withEmail(VALID_EMAIL_AMY).build();
+ expectedCommand = new EditUserCommand(descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // address
+ userInput = ADDRESS_DESC_AMY;
+ descriptor = new EditUserDescriptorBuilder().withAddress(VALID_ADDRESS_AMY).build();
+ expectedCommand = new EditUserCommand(descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // tags
+ userInput = TAG_DESC_FRIEND;
+ descriptor = new EditUserDescriptorBuilder().withTags(VALID_TAG_FRIEND).build();
+ expectedCommand = new EditUserCommand(descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+
+ @Test
+ public void parse_multipleRepeatedFields_failure() {
+ // More extensive testing of duplicate parameter detections is done in
+ // AddCommandParserTest#parse_repeatedNonTagValue_failure()
+
+ // valid followed by invalid
+ String userInput = INVALID_PHONE_DESC + PHONE_DESC_BOB;
+
+ assertParseFailure(parser, userInput, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
+
+ // invalid followed by valid
+ userInput = PHONE_DESC_BOB + INVALID_PHONE_DESC;
+
+ assertParseFailure(parser, userInput, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE));
+
+ // mulltiple valid fields repeated
+ userInput = 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;
+
+ assertParseFailure(parser, userInput,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS));
+
+ // multiple invalid values
+ userInput = INVALID_PHONE_DESC + INVALID_ADDRESS_DESC + INVALID_EMAIL_DESC
+ + INVALID_PHONE_DESC + INVALID_ADDRESS_DESC + INVALID_EMAIL_DESC;
+
+ assertParseFailure(parser, userInput,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS));
+ }
+
+ @Test
+ public void parse_resetTags_success() {
+ String userInput = TAG_EMPTY;
+
+ EditUserDescriptor descriptor = new EditUserDescriptorBuilder().withTags().build();
+ EditUserCommand expectedCommand = new EditUserCommand(descriptor);
+
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
index 4256788b1a7..9612124f001 100644
--- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
+++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
@@ -28,7 +28,7 @@ public class ParserUtilTest {
private static final String INVALID_TAG = "#friend";
private static final String VALID_NAME = "Rachel Walker";
- private static final String VALID_PHONE = "123456";
+ private static final String VALID_PHONE = "12345678";
private static final String VALID_ADDRESS = "123 Main Street #0505";
private static final String VALID_EMAIL = "rachel@example.com";
private static final String VALID_TAG_1 = "friend";
diff --git a/src/test/java/seedu/address/logic/parser/RemoveEventCommandParserTest.java b/src/test/java/seedu/address/logic/parser/RemoveEventCommandParserTest.java
new file mode 100644
index 00000000000..bb04836a951
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/RemoveEventCommandParserTest.java
@@ -0,0 +1,53 @@
+package seedu.address.logic.parser;
+
+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.commons.core.index.Index;
+import seedu.address.logic.commands.RemoveEventCommand;
+
+public class RemoveEventCommandParserTest {
+ private RemoveEventCommandParser parser = new RemoveEventCommandParser();
+
+ @Test
+ public void parse_allFieldsPresent_success() {
+
+ // dated event in user
+ assertParseSuccess(parser, "user en/CS2103 Meeting",
+ new RemoveEventCommand("CS2103 MEETING", null));
+
+ // dated event in friend
+ assertParseSuccess(parser, "1 en/CS2103 Meeting",
+ new RemoveEventCommand("CS2103 MEETING", Index.fromOneBased(1)));
+ }
+
+ @Test
+ public void parse_repeatedValue_failure() {
+ String validCommand = "user en/CS2103 Meeting";
+
+ // multiple event name
+ assertParseFailure(parser, validCommand + " en/CS2103 Meeting",
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: en/ ");
+
+ }
+
+ @Test
+ public void parse_fieldMissing_failure() {
+ // missing event name prefix
+ assertParseFailure(parser, "user",
+ "Missing prefix(es) for en/ !\n" + "Message Usage:\n" + RemoveEventCommand.MESSAGE_USAGE);
+
+ // all prefixes missing
+ assertParseFailure(parser, "user CS2103 Meeting",
+ "Missing prefix(es) for en/ !\n" + "Message Usage:\n"
+ + RemoveEventCommand.MESSAGE_USAGE);
+
+ //wrong index
+ assertParseFailure(parser, "wrong en/CS2103 Meeting",
+ String.format("Invalid index!\n"
+ + "Index can only be 'user' or a positive integer! \n"));
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/RemoveReminderCommandParserTest.java b/src/test/java/seedu/address/logic/parser/RemoveReminderCommandParserTest.java
new file mode 100644
index 00000000000..db2109323b1
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/RemoveReminderCommandParserTest.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+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.commands.RemoveReminderCommand;
+
+public class RemoveReminderCommandParserTest {
+
+ private RemoveReminderCommandParser parser = new RemoveReminderCommandParser();
+
+ @Test
+ public void parse_emptyArg_throwsParseException() {
+ assertParseFailure(parser, " ",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemoveReminderCommand.MESSAGE_USAGE));
+ }
+
+ @Test
+ public void parse_validArgs_returnsRemoveReminderCommand() {
+ // no leading and trailing whitespaces
+ RemoveReminderCommand expectedRemoveReminderCommand = new RemoveReminderCommand("CS2103 Meeting");
+ assertParseSuccess(parser, "CS2103 Meeting", expectedRemoveReminderCommand);
+
+ // multiple whitespaces between keywords
+ assertParseSuccess(parser, " \n CS2103 Meeting \t", expectedRemoveReminderCommand);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/RemoveScheduleCommandParserTest.java b/src/test/java/seedu/address/logic/parser/RemoveScheduleCommandParserTest.java
new file mode 100644
index 00000000000..630b5a3337a
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/RemoveScheduleCommandParserTest.java
@@ -0,0 +1,74 @@
+package seedu.address.logic.parser;
+
+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.commons.core.index.Index;
+import seedu.address.logic.commands.RemoveScheduleCommand;
+
+public class RemoveScheduleCommandParserTest {
+ private final RemoveScheduleCommandParser parser = new RemoveScheduleCommandParser();
+
+ @Test
+ public void parse_allFieldsPresent_success() {
+
+ // cca in user
+ assertParseSuccess(parser, "user type/cca en/Basketball",
+ new RemoveScheduleCommand("BASKETBALL", "cca"));
+
+ // cca in friend
+ assertParseSuccess(parser, "1 type/cca en/Basketball",
+ new RemoveScheduleCommand("BASKETBALL", "cca", Index.fromOneBased(1)));
+
+ // module in user
+ assertParseSuccess(parser, "user type/module en/CS2103",
+ new RemoveScheduleCommand("CS2103", "module"));
+
+ // module in friend
+ assertParseSuccess(parser, "1 type/module en/CS2103",
+ new RemoveScheduleCommand("CS2103", "module", Index.fromOneBased(1)));
+ }
+
+ @Test
+ public void parse_repeatedValue_failure() {
+ String validCommand = "user type/cca en/Basketball";
+
+ // multiple event type
+ assertParseFailure(parser, validCommand + " type/cca",
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: type/ ");
+
+ // multiple event name
+ assertParseFailure(parser, validCommand + " en/Basketball",
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: en/ ");
+
+ // multiple fields repeated
+ assertParseFailure(parser, validCommand + " type/cca" + " en/Basketball",
+ "You can only have 1 of each prefix!\n" + "Duplicated prefixes are: en/ type/ ");
+ }
+
+ @Test
+ public void parse_fieldMissing_failure() {
+
+ // missing schedule name prefix
+ assertParseFailure(parser, "user type/cca",
+ "Missing prefix(es) for en/ !\n" + "Message Usage:\n"
+ + RemoveScheduleCommand.MESSAGE_USAGE);
+
+ // missing schedule type prefix
+ assertParseFailure(parser, "user en/Basketball",
+ "Missing prefix(es) for type/ !\n" + "Message Usage:\n"
+ + RemoveScheduleCommand.MESSAGE_USAGE);
+
+ // all prefixes missing
+ assertParseFailure(parser, "user cca Basketball",
+ "Missing prefix(es) for en/ type/ !\n" + "Message Usage:\n"
+ + RemoveScheduleCommand.MESSAGE_USAGE);
+
+ // wrong index
+ assertParseFailure(parser, "wrong type/cca en/Basketball",
+ "Invalid index!\n" + "Index can only be 'user' or a positive integer! \n");
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/parser/SetReminderCommandParserTest.java b/src/test/java/seedu/address/logic/parser/SetReminderCommandParserTest.java
new file mode 100644
index 00000000000..d7fc11f5c93
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/SetReminderCommandParserTest.java
@@ -0,0 +1,29 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+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.commands.SetReminderCommand;
+
+public class SetReminderCommandParserTest {
+ private SetReminderCommandParser parser = new SetReminderCommandParser();
+
+ @Test
+ public void parse_emptyArg_throwsParseException() {
+ assertParseFailure(parser, " ",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, SetReminderCommand.MESSAGE_USAGE));
+ }
+
+ @Test
+ public void parse_validArgs_returnsRemoveReminderCommand() {
+ // no leading and trailing whitespaces
+ SetReminderCommand expectedSetReminderCommand = new SetReminderCommand("CS2103 Meeting");
+ assertParseSuccess(parser, "CS2103 Meeting", expectedSetReminderCommand);
+
+ // multiple whitespaces between keywords
+ assertParseSuccess(parser, " \n CS2103 Meeting \t", expectedSetReminderCommand);
+ }
+}
diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java
index 68c8c5ba4d5..a82304e615a 100644
--- a/src/test/java/seedu/address/model/AddressBookTest.java
+++ b/src/test/java/seedu/address/model/AddressBookTest.java
@@ -9,6 +9,7 @@
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -89,6 +90,40 @@ public void toStringMethod() {
assertEquals(expected, addressBook.toString());
}
+ @Test
+ public void equalsMethod() {
+ AddressBook addressBook = new AddressBook();
+ AddressBook addressBook2 = new AddressBook();
+ assertTrue(addressBook.equals(addressBook2));
+ addressBook.addPerson(ALICE);
+ assertFalse(addressBook.equals(addressBook2));
+ assertFalse(addressBook.equals(null));
+ }
+
+ @Test
+ public void hashCodeMethod() {
+ AddressBook addressBook = new AddressBook();
+ AddressBook addressBook2 = new AddressBook();
+ assertEquals(addressBook.hashCode(), addressBook2.hashCode());
+ }
+
+ @Test
+ public void getBirthdayList() {
+ AddressBook addressBook = new AddressBook();
+ assertEquals("", addressBook.getBirthdayList());
+ addressBook.addPerson(new PersonBuilder().withName("Alice Pauline")
+ .withBirthday(LocalDate.now().toString()).build());
+ assertEquals("Alice Pauline\n", addressBook.getBirthdayList());
+ }
+
+ @Test
+ public void getPersonWithName() {
+ addressBook.addPerson(ALICE);
+ Person expectedPerson = ALICE;
+ Person actualPerson = addressBook.getPersonWithName(expectedPerson.getName());
+ assertEquals(expectedPerson, actualPerson);
+ }
+
/**
* A stub ReadOnlyAddressBook whose persons list can violate interface constraints.
*/
diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java
index 2cf1418d116..cb016d338a8 100644
--- a/src/test/java/seedu/address/model/ModelManagerTest.java
+++ b/src/test/java/seedu/address/model/ModelManagerTest.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 static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
import static seedu.address.testutil.Assert.assertThrows;
@@ -10,13 +11,20 @@
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.time.LocalDate;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
+import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.Person;
+import seedu.address.model.user.User;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
import seedu.address.testutil.AddressBookBuilder;
+import seedu.address.testutil.PersonBuilder;
public class ModelManagerTest {
@@ -98,28 +106,29 @@ public void equals() {
AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build();
AddressBook differentAddressBook = new AddressBook();
UserPrefs userPrefs = new UserPrefs();
+ UserData userData = new UserData();
// same values -> returns true
- modelManager = new ModelManager(addressBook, userPrefs);
- ModelManager modelManagerCopy = new ModelManager(addressBook, userPrefs);
- assertTrue(modelManager.equals(modelManagerCopy));
+ modelManager = new ModelManager(addressBook, userPrefs, userData);
+ ModelManager modelManagerCopy = new ModelManager(addressBook, userPrefs, userData);
+ assertEquals(modelManager, modelManagerCopy);
// same object -> returns true
- assertTrue(modelManager.equals(modelManager));
+ assertEquals(modelManager, modelManager);
// null -> returns false
- assertFalse(modelManager.equals(null));
+ assertNotEquals(null, modelManager);
// different types -> returns false
- assertFalse(modelManager.equals(5));
+ assertNotEquals(5, modelManager);
// different addressBook -> returns false
- assertFalse(modelManager.equals(new ModelManager(differentAddressBook, userPrefs)));
+ assertNotEquals(modelManager, new ModelManager(differentAddressBook, userPrefs, userData));
// different filteredList -> returns false
String[] keywords = ALICE.getName().fullName.split("\\s+");
modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(keywords)));
- assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs)));
+ assertNotEquals(modelManager, new ModelManager(addressBook, userPrefs, userData));
// resets modelManager to initial state for upcoming tests
modelManager.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
@@ -127,6 +136,39 @@ public void equals() {
// different userPrefs -> returns false
UserPrefs differentUserPrefs = new UserPrefs();
differentUserPrefs.setAddressBookFilePath(Paths.get("differentFilePath"));
- assertFalse(modelManager.equals(new ModelManager(addressBook, differentUserPrefs)));
+ assertNotEquals(modelManager, new ModelManager(addressBook, differentUserPrefs, userData));
+ }
+
+ @Test
+ public void getUserView() {
+ UserData userData = new UserData();
+ ModelManager modelManager = new ModelManager();
+ ObservableList userView = userData.getUserView();
+ modelManager.setUser(userData.getUser());
+ assertEquals(modelManager.getUserView(), userView);
+ }
+
+ @Test
+ public void setUserData() {
+ UserData userData = new UserData();
+ ModelManager modelManager = new ModelManager();
+ modelManager.setUserData(userData);
+ assertEquals(modelManager.getUserData(), userData);
+ }
+
+ @Test
+ public void getBirthdayList() {
+ ModelManager modelManager = new ModelManager();
+ modelManager.addPerson(new PersonBuilder().withName("Alice Pauline")
+ .withBirthday(LocalDate.now().toString()).build());
+ assertEquals("Alice Pauline\n", modelManager.getBirthdayList());
+ }
+
+ @Test
+ public void getPersonWithName() {
+ modelManager.addPerson(ALICE);
+ Person expectedPerson = ALICE;
+ Person actualPerson = modelManager.getPersonWithName(expectedPerson.getName());
+ assertEquals(expectedPerson, actualPerson);
}
}
diff --git a/src/test/java/seedu/address/model/UserPrefsTest.java b/src/test/java/seedu/address/model/UserPrefsTest.java
deleted file mode 100644
index b1307a70d52..00000000000
--- a/src/test/java/seedu/address/model/UserPrefsTest.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package seedu.address.model;
-
-import static seedu.address.testutil.Assert.assertThrows;
-
-import org.junit.jupiter.api.Test;
-
-public class UserPrefsTest {
-
- @Test
- public void setGuiSettings_nullGuiSettings_throwsNullPointerException() {
- UserPrefs userPref = new UserPrefs();
- assertThrows(NullPointerException.class, () -> userPref.setGuiSettings(null));
- }
-
- @Test
- public void setAddressBookFilePath_nullPath_throwsNullPointerException() {
- UserPrefs userPrefs = new UserPrefs();
- assertThrows(NullPointerException.class, () -> userPrefs.setAddressBookFilePath(null));
- }
-
-}
diff --git a/src/test/java/seedu/address/model/person/AddressTest.java b/src/test/java/seedu/address/model/person/AddressTest.java
index 314885eca26..a8b0047be6d 100644
--- a/src/test/java/seedu/address/model/person/AddressTest.java
+++ b/src/test/java/seedu/address/model/person/AddressTest.java
@@ -1,5 +1,6 @@
package seedu.address.model.person;
+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.testutil.Assert.assertThrows;
@@ -13,22 +14,13 @@ public void constructor_null_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> new Address(null));
}
- @Test
- public void constructor_invalidAddress_throwsIllegalArgumentException() {
- String invalidAddress = "";
- assertThrows(IllegalArgumentException.class, () -> new Address(invalidAddress));
- }
-
@Test
public void isValidAddress() {
// null address
assertThrows(NullPointerException.class, () -> Address.isValidAddress(null));
- // invalid addresses
- assertFalse(Address.isValidAddress("")); // empty string
- assertFalse(Address.isValidAddress(" ")); // spaces only
-
// valid addresses
+ assertTrue(Address.isValidAddress("")); // empty string
assertTrue(Address.isValidAddress("Blk 456, Den Road, #01-355"));
assertTrue(Address.isValidAddress("-")); // one character
assertTrue(Address.isValidAddress("Leng Inc; 1234 Market St; San Francisco CA 2349879; USA")); // long address
@@ -53,4 +45,10 @@ public void equals() {
// different values -> returns false
assertFalse(address.equals(new Address("Other Valid Address")));
}
+
+ @Test
+ public void testHashCode() {
+ Address address = new Address("Valid Address");
+ assertEquals(address.hashCode(), new Address("Valid Address").hashCode());
+ }
}
diff --git a/src/test/java/seedu/address/model/person/BirthdayTest.java b/src/test/java/seedu/address/model/person/BirthdayTest.java
new file mode 100644
index 00000000000..903caaf04cc
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/BirthdayTest.java
@@ -0,0 +1,32 @@
+package seedu.address.model.person;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.time.LocalDate;
+
+import org.junit.jupiter.api.Test;
+
+public class BirthdayTest {
+
+ @Test
+ public void equals() {
+ Birthday birthday = new Birthday("2001-04-19");
+ assertFalse(birthday.equals(null));
+ }
+
+ @Test
+ public void isValidBirthday() {
+ assertFalse(Birthday.isValidBirthday("2000-02-30"));
+ assertTrue(Birthday.isValidBirthday(""));
+ assertTrue(Birthday.isValidBirthday("2000-01-15"));
+ }
+
+ @Test
+ public void getDate() {
+ Birthday birthday = new Birthday("2001-04-19");
+ assertTrue(birthday.getDate().equals(LocalDate.parse("2001-04-19")));
+ birthday = new Birthday("");
+ assertTrue(birthday.getDate().equals(LocalDate.parse("1900-01-01")));
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/EmailTest.java b/src/test/java/seedu/address/model/person/EmailTest.java
index f08cdff0a64..94873143504 100644
--- a/src/test/java/seedu/address/model/person/EmailTest.java
+++ b/src/test/java/seedu/address/model/person/EmailTest.java
@@ -1,5 +1,6 @@
package seedu.address.model.person;
+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.testutil.Assert.assertThrows;
@@ -15,7 +16,7 @@ public void constructor_null_throwsNullPointerException() {
@Test
public void constructor_invalidEmail_throwsIllegalArgumentException() {
- String invalidEmail = "";
+ String invalidEmail = "@example.com";
assertThrows(IllegalArgumentException.class, () -> new Email(invalidEmail));
}
@@ -24,10 +25,6 @@ public void isValidEmail() {
// null email
assertThrows(NullPointerException.class, () -> Email.isValidEmail(null));
- // blank email
- assertFalse(Email.isValidEmail("")); // empty string
- assertFalse(Email.isValidEmail(" ")); // spaces only
-
// missing parts
assertFalse(Email.isValidEmail("@example.com")); // missing local part
assertFalse(Email.isValidEmail("peterjackexample.com")); // missing '@' symbol
@@ -53,6 +50,7 @@ public void isValidEmail() {
assertFalse(Email.isValidEmail("peterjack@example.c")); // top level domain has less than two chars
// valid email
+ assertTrue(Email.isValidEmail(""));
assertTrue(Email.isValidEmail("PeterJack_1190@example.com")); // underscore in local part
assertTrue(Email.isValidEmail("PeterJack.1190@example.com")); // period in local part
assertTrue(Email.isValidEmail("PeterJack+1190@example.com")); // '+' symbol in local part
@@ -85,4 +83,11 @@ public void equals() {
// different values -> returns false
assertFalse(email.equals(new Email("other.valid@email")));
}
+
+ @Test
+ public void testHashCode() {
+ Email email = new Email("valid@email");
+ assertEquals(email.hashCode(), new Email("valid@email").hashCode());
+ }
+
}
diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
index 6b3fd90ade7..e896c4b5e10 100644
--- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
+++ b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
@@ -69,8 +69,8 @@ public void test_nameDoesNotContainKeywords_returnsFalse() {
assertFalse(predicate.test(new PersonBuilder().withName("Alice Bob").build()));
// Keywords match phone, email and address, but does not match name
- predicate = new NameContainsKeywordsPredicate(Arrays.asList("12345", "alice@email.com", "Main", "Street"));
- assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345")
+ predicate = new NameContainsKeywordsPredicate(Arrays.asList("12345678", "alice@email.com", "Main", "Street"));
+ assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345678")
.withEmail("alice@email.com").withAddress("Main Street").build()));
}
diff --git a/src/test/java/seedu/address/model/person/NameTest.java b/src/test/java/seedu/address/model/person/NameTest.java
index 94e3dd726bd..808b5b0f4e6 100644
--- a/src/test/java/seedu/address/model/person/NameTest.java
+++ b/src/test/java/seedu/address/model/person/NameTest.java
@@ -25,8 +25,6 @@ public void isValidName() {
assertThrows(NullPointerException.class, () -> Name.isValidName(null));
// invalid name
- assertFalse(Name.isValidName("")); // empty string
- assertFalse(Name.isValidName(" ")); // spaces only
assertFalse(Name.isValidName("^")); // only non-alphanumeric characters
assertFalse(Name.isValidName("peter*")); // contains non-alphanumeric characters
diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/address/model/person/PersonTest.java
index 31a10d156c9..2b547383853 100644
--- a/src/test/java/seedu/address/model/person/PersonTest.java
+++ b/src/test/java/seedu/address/model/person/PersonTest.java
@@ -24,6 +24,18 @@ public void asObservableList_modifyList_throwsUnsupportedOperationException() {
assertThrows(UnsupportedOperationException.class, () -> person.getTags().remove(0));
}
+ @Test
+ public void isSamePhone() {
+ assertTrue(ALICE.isSamePhone(ALICE));
+ assertFalse(ALICE.isSamePerson(new PersonBuilder().withName(VALID_NAME_BOB).build()));
+ }
+
+ @Test
+ public void isSameEmail() {
+ assertTrue(ALICE.isSameEmail(ALICE));
+ assertFalse(ALICE.isSamePerson(new PersonBuilder().withName(VALID_NAME_BOB).build()));
+ }
+
@Test
public void isSamePerson() {
// same object -> returns true
@@ -90,10 +102,18 @@ public void equals() {
assertFalse(ALICE.equals(editedAlice));
}
+ @Test
+ public void testHashCode() {
+ Person alice = new PersonBuilder(ALICE).build();
+ assertEquals(alice.hashCode(), new PersonBuilder(ALICE).build().hashCode());
+ }
+
@Test
public void toStringMethod() {
String expected = Person.class.getCanonicalName() + "{name=" + ALICE.getName() + ", phone=" + ALICE.getPhone()
- + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress() + ", tags=" + ALICE.getTags() + "}";
+ + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress()
+ + ", birthday=" + ALICE.getBirthday() + ", free times=" + ALICE.getSchedule()
+ + ", tags=" + ALICE.getTags() + "}";
assertEquals(expected, ALICE.toString());
}
}
diff --git a/src/test/java/seedu/address/model/person/PhoneTest.java b/src/test/java/seedu/address/model/person/PhoneTest.java
index deaaa5ba190..8b6cd4f0801 100644
--- a/src/test/java/seedu/address/model/person/PhoneTest.java
+++ b/src/test/java/seedu/address/model/person/PhoneTest.java
@@ -1,5 +1,6 @@
package seedu.address.model.person;
+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.testutil.Assert.assertThrows;
@@ -15,7 +16,7 @@ public void constructor_null_throwsNullPointerException() {
@Test
public void constructor_invalidPhone_throwsIllegalArgumentException() {
- String invalidPhone = "";
+ String invalidPhone = "123";
assertThrows(IllegalArgumentException.class, () -> new Phone(invalidPhone));
}
@@ -25,25 +26,22 @@ public void isValidPhone() {
assertThrows(NullPointerException.class, () -> Phone.isValidPhone(null));
// invalid phone numbers
- assertFalse(Phone.isValidPhone("")); // empty string
- assertFalse(Phone.isValidPhone(" ")); // spaces only
assertFalse(Phone.isValidPhone("91")); // less than 3 numbers
assertFalse(Phone.isValidPhone("phone")); // non-numeric
assertFalse(Phone.isValidPhone("9011p041")); // alphabets within digits
assertFalse(Phone.isValidPhone("9312 1534")); // spaces within digits
// valid phone numbers
- assertTrue(Phone.isValidPhone("911")); // exactly 3 numbers
- assertTrue(Phone.isValidPhone("93121534"));
- assertTrue(Phone.isValidPhone("124293842033123")); // long phone numbers
+ assertTrue(Phone.isValidPhone("93121534")); // exactly 8 numbers
+ assertTrue(Phone.isValidPhone(""));
}
@Test
public void equals() {
- Phone phone = new Phone("999");
+ Phone phone = new Phone("91792309");
// same values -> returns true
- assertTrue(phone.equals(new Phone("999")));
+ assertTrue(phone.equals(new Phone("91792309")));
// same object -> returns true
assertTrue(phone.equals(phone));
@@ -55,6 +53,13 @@ public void equals() {
assertFalse(phone.equals(5.0f));
// different values -> returns false
- assertFalse(phone.equals(new Phone("995")));
+ assertFalse(phone.equals(new Phone("91378808")));
+ assertFalse(phone.equals(new Phone("99599999")));
+ }
+
+ @Test
+ public void testHashCode() {
+ Phone phone = new Phone("99999999");
+ assertEquals(phone.hashCode(), new Phone("99999999").hashCode());
}
}
diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/address/model/person/UniquePersonListTest.java
index 17ae501df08..cedb540acda 100644
--- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java
+++ b/src/test/java/seedu/address/model/person/UniquePersonListTest.java
@@ -2,6 +2,8 @@
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.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
@@ -58,6 +60,17 @@ public void add_duplicatePerson_throwsDuplicatePersonException() {
assertThrows(DuplicatePersonException.class, () -> uniquePersonList.add(ALICE));
}
+ @Test
+ public void getPersonWithName_nameNotInList_returnNull() {
+ assertNull(uniquePersonList.getPersonWithName(new Name("Edric")));
+ }
+
+ @Test
+ public void getPersonWithName_nameInList_returnNull() {
+ uniquePersonList.add(ALICE);
+ assertEquals(ALICE, uniquePersonList.getPersonWithName(ALICE.getName()));
+ }
+
@Test
public void setPerson_nullTargetPerson_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> uniquePersonList.setPerson(null, ALICE));
@@ -168,8 +181,60 @@ public void asUnmodifiableObservableList_modifyList_throwsUnsupportedOperationEx
-> uniquePersonList.asUnmodifiableObservableList().remove(0));
}
+ @Test
+ public void iterator() {
+ UniquePersonList uniquePersonList = new UniquePersonList();
+ Person alice = new PersonBuilder(ALICE).build();
+ Person bob = new PersonBuilder(BOB).build();
+ uniquePersonList.add(alice);
+ uniquePersonList.add(bob);
+
+ int count = 0;
+ for (Person person : uniquePersonList) {
+ if (count == 0) {
+ assertEquals(alice, person);
+ } else if (count == 1) {
+ assertEquals(bob, person);
+ }
+ count++;
+ }
+ assertEquals(2, count);
+ }
+
+ @Test
+ public void testEquals() {
+ UniquePersonList uniquePersonList1 = new UniquePersonList();
+ UniquePersonList uniquePersonList2 = new UniquePersonList();
+ Person alice = new PersonBuilder(ALICE).build();
+ Person bob = new PersonBuilder(BOB).build();
+ uniquePersonList1.add(alice);
+ uniquePersonList1.add(bob);
+ uniquePersonList2.add(alice);
+ uniquePersonList2.add(bob);
+
+ assertEquals(uniquePersonList1, uniquePersonList1);
+ assertNotEquals(uniquePersonList1, null);
+ }
+
+ @Test
+ public void testHashCode() {
+ UniquePersonList uniquePersonList1 = new UniquePersonList();
+ UniquePersonList uniquePersonList2 = new UniquePersonList();
+ Person alice = new PersonBuilder(ALICE).build();
+ Person bob = new PersonBuilder(BOB).build();
+ uniquePersonList1.add(alice);
+ uniquePersonList1.add(bob);
+ uniquePersonList2.add(alice);
+ uniquePersonList2.add(bob);
+
+ // Test hashCode method
+ assertEquals(uniquePersonList1.hashCode(), uniquePersonList2.hashCode());
+ }
+
@Test
public void toStringMethod() {
assertEquals(uniquePersonList.asUnmodifiableObservableList().toString(), uniquePersonList.toString());
}
+
+
}
diff --git a/src/test/java/seedu/address/model/person/timetable/CcaTest.java b/src/test/java/seedu/address/model/person/timetable/CcaTest.java
new file mode 100644
index 00000000000..b67eb728fd8
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/timetable/CcaTest.java
@@ -0,0 +1,112 @@
+package seedu.address.model.person.timetable;
+
+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.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+
+public class CcaTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Cca(null, "monday 1200 1400"));
+ }
+
+ @Test
+ public void constructor_invalidName_throwsIllegalArgumentException() {
+ String invalidName = "";
+ assertThrows(IllegalArgumentException.class, () -> new Cca(invalidName, "monday 1200 1400"));
+ }
+
+ @Test
+ public void newCca_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> Cca.newCca(null));
+ }
+
+ @Test
+ public void newCca_invalidInput_throwsIllegalArgumentException() {
+ String invalidInput = "monday 1200 1400";
+ assertThrows(IllegalArgumentException.class, () -> Cca.newCca(invalidInput));
+ }
+
+ @Test
+ public void newCca_invalidName_throwsIllegalValueException() {
+ String invalidInput = "@ monday 1200 1400";
+ assertThrows(IllegalValueException.class, () -> Cca.newCca(invalidInput));
+ }
+
+ @Test
+ public void newCca_validInput_success() throws IllegalValueException {
+ String validInput = "badminton monday 1200 1400";
+ assertEquals(new Cca("badminton", "monday 1200 1400"), Cca.newCca(validInput));
+ }
+
+ @Test
+ public void newCca_validInputSpaceBetweenName_success() throws IllegalValueException {
+ String validInput = "badminton 2 monday 1200 1400";
+ assertEquals(new Cca("badminton 2", "monday 1200 1400"), Cca.newCca(validInput));
+ }
+
+ @Test
+ public void isCca_success() {
+ Cca cca = new Cca("badminton", "monday 1200 1400");
+ assertTrue(cca.isCca());
+ }
+
+ @Test
+ public void getType_success() {
+ Cca cca = new Cca("badminton", "monday 1200 1400");
+ assertEquals("CCA", cca.getType());
+ }
+
+ @Test
+ public void isValidCcaName() {
+ // null name
+ assertThrows(NullPointerException.class, () -> Cca.isValidCcaName(null));
+
+ // invalid name
+ assertFalse(Cca.isValidCcaName("")); // empty string
+ assertFalse(Cca.isValidCcaName(" ")); // spaces only
+ assertFalse(Cca.isValidCcaName("^")); // only non-alphanumeric characters
+ assertFalse(Cca.isValidCcaName("basketball*")); // contains non-alphanumeric characters
+
+ // valid name
+ assertTrue(Cca.isValidCcaName("badminton")); // alphabets only
+ assertTrue(Cca.isValidCcaName("12345")); // numbers only
+ assertTrue(Cca.isValidCcaName("badminton 1")); // alphanumeric characters
+ assertTrue(Cca.isValidCcaName("Badminton 2")); // with capital letters
+ assertTrue(Cca.isValidCcaName("Badminton is a good sport")); // long names
+ }
+
+ @Test
+ public void equals() {
+ Cca cca = new Cca("Valid Cca", "monday 1200 1400");
+
+ // same values -> returns true
+ assertTrue(cca.equals(new Cca("Valid Cca", "monday 1200 1400")));
+
+ // same object -> returns true
+ assertTrue(cca.equals(cca));
+
+ // null -> returns false
+ assertFalse(cca.equals(null));
+
+ // different types -> returns false
+ assertFalse(cca.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(cca.equals(new Cca("Other Valid Name", "monday 1200 1400")));
+
+ assertFalse(cca.equals(new Cca("Valid Cca", "tuesday 1200 1400")));
+ }
+
+ @Test
+ public void testHashCode() {
+ Cca cca = new Cca("Valid Cca", "monday 1200 1400");
+ assertEquals(cca.hashCode(), new Cca("Valid Cca", "monday 1200 1400").hashCode());
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/timetable/DatedEventTest.java b/src/test/java/seedu/address/model/person/timetable/DatedEventTest.java
new file mode 100644
index 00000000000..8d25a61d9d8
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/timetable/DatedEventTest.java
@@ -0,0 +1,65 @@
+package seedu.address.model.person.timetable;
+
+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 org.junit.jupiter.api.Test;
+
+public class DatedEventTest {
+ @Test
+ public void isDatedEvent_success() {
+ DatedEvent datedEvent = new DatedEvent("badminton", "monday 1200 1400",
+ "2023-10-30", true);
+ assertTrue(datedEvent.isDatedEvent());
+ }
+
+ @Test
+ public void getType_success() {
+ DatedEvent datedEvent = new DatedEvent("badminton", "monday 1200 1400",
+ "2023-10-30", true);
+ assertEquals("Event", datedEvent.getType());
+ }
+
+ @Test
+ public void getStringForReminder_success() {
+ DatedEvent datedEvent = new DatedEvent("badminton", "monday 1200 1400",
+ "2023-10-30", true);
+ assertEquals("badminton Monday 1200 1400", datedEvent.getStringForReminder());
+ }
+
+ @Test
+ public void equals() {
+ DatedEvent datedEvent = new DatedEvent("Valid Dated Event", "monday 1200 1400",
+ "2023-10-30", true);
+
+ // same values -> returns true
+ assertTrue(datedEvent.equals(new DatedEvent("Valid Dated Event", "monday 1200 1400",
+ "2023-10-30", true)));
+
+ // same object -> returns true
+ assertTrue(datedEvent.equals(datedEvent));
+
+ // null -> returns false
+ assertFalse(datedEvent.equals(null));
+
+ // different types -> returns false
+ assertFalse(datedEvent.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(datedEvent.equals(new DatedEvent("Other Valid Name", "monday 1200 1400",
+ "2023-10-30", true)));
+
+ assertFalse(datedEvent.equals(new DatedEvent("Valid Cca", "tuesday 1200 1400",
+ "2023-10-30", true)));
+ }
+
+ @Test
+ public void testHashCode() {
+ DatedEvent datedEvent = new DatedEvent("Valid Dated Event", "monday 1200 1400",
+ "2023-10-30", true);
+ assertEquals(datedEvent.hashCode(), new DatedEvent("Valid Dated Event", "monday 1200 1400",
+ "2023-10-30", true).hashCode());
+ }
+
+}
diff --git a/src/test/java/seedu/address/model/person/timetable/FreeTimeTest.java b/src/test/java/seedu/address/model/person/timetable/FreeTimeTest.java
new file mode 100644
index 00000000000..32c387ace69
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/timetable/FreeTimeTest.java
@@ -0,0 +1,107 @@
+package seedu.address.model.person.timetable;
+
+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.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class FreeTimeTest {
+
+ @Test
+ public void createValidFreeTime() {
+ // Test creating a valid FreeTime object
+ FreeTime freeTime = new FreeTime("Monday 0800 1000");
+ assertEquals("[Monday 0800 1000]", freeTime.toString());
+ }
+
+ @Test
+ public void createValidFreeTimeCaseInsensitive() {
+ // Test creating a valid FreeTime object with case-insensitive day
+ FreeTime freeTime = new FreeTime("tuesday 1400 1500");
+ assertEquals("[Tuesday 1400 1500]", freeTime.toString());
+ }
+
+ @Test
+ public void isValidFreeTime() {
+ assertFalse(FreeTime.isValidFreeTime("")); // empty string
+ assertTrue(FreeTime.isValidFreeTime("Monday 0800 1000")); // valid FreeTime
+ }
+
+ @Test
+ public void compareTo() {
+ FreeTime freeTime1 = new FreeTime("Monday 0800 1000");
+ FreeTime freeTime2 = new FreeTime("Monday 0900 1100");
+ FreeTime freeTime3 = new FreeTime("Tuesday 0900 1100");
+
+ // Test compareTo
+ assertTrue(freeTime1.compareTo(freeTime2) < 0);
+ assertTrue(freeTime2.compareTo(freeTime1) > 0);
+ assertTrue(freeTime1.compareTo(freeTime3) < 0);
+ }
+
+ @Test
+ public void overlap() {
+ FreeTime freeTime1 = new FreeTime("Monday 0800 1000");
+ FreeTime freeTime2 = new FreeTime("Monday 0900 1100");
+ FreeTime freeTime3 = new FreeTime("Tuesday 0900 1100");
+ FreeTime freeTime4 = new FreeTime("Monday 1200 1300");
+
+ // Test overlap
+ FreeTime overlap = freeTime1.overlap(freeTime2);
+ assertEquals("[Monday 0900 1000]", overlap.toString());
+
+ overlap = freeTime1.overlap(freeTime3);
+ assertNull(overlap);
+
+ overlap = freeTime1.overlap(freeTime4);
+ assertNull(overlap);
+ }
+
+ @Test
+ public void isOverlap() {
+ FreeTime freeTime1 = new FreeTime("Monday 0800 1000");
+ FreeTime freeTime2 = new FreeTime("Monday 0900 1100");
+ FreeTime freeTime3 = new FreeTime("Tuesday 0900 1100");
+
+ // Test isOverlap
+ assertTrue(freeTime1.isOverlap(freeTime2));
+ assertFalse(freeTime1.isOverlap(freeTime3));
+ }
+
+ @Test
+ public void equals() {
+ FreeTime freeTime1 = new FreeTime("Monday 0800 1000");
+ FreeTime freeTime2 = new FreeTime("Monday 0800 1000");
+ FreeTime freeTime3 = new FreeTime("Tuesday 0900 1100");
+
+ // Test equals
+ assertTrue(freeTime1.equals(freeTime1));
+ assertTrue(freeTime1.equals(freeTime2));
+ assertFalse(freeTime1.equals(freeTime3));
+ assertFalse(freeTime1.equals(null));
+
+ }
+
+ @Test
+ public void testHashCode() {
+ FreeTime freeTime1 = new FreeTime("Monday 0800 1000");
+ FreeTime freeTime2 = new FreeTime("Monday 0800 1000");
+ FreeTime freeTime3 = new FreeTime("Tuesday 0900 1100");
+
+ // Test hashCode
+ assertEquals(freeTime1.hashCode(), freeTime2.hashCode());
+ assertNotEquals(freeTime1.hashCode(), freeTime3.hashCode());
+ }
+
+ @Test
+ public void testToString() {
+ FreeTime freeTime1 = new FreeTime("Monday 0800 1000");
+
+ // Test toString
+ assertEquals("[Monday 0800 1000]", freeTime1.toString());
+ }
+
+}
diff --git a/src/test/java/seedu/address/model/person/timetable/HalfHourBlocksTest.java b/src/test/java/seedu/address/model/person/timetable/HalfHourBlocksTest.java
new file mode 100644
index 00000000000..ffcdffcda72
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/timetable/HalfHourBlocksTest.java
@@ -0,0 +1,86 @@
+package seedu.address.model.person.timetable;
+
+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 org.junit.jupiter.api.Test;
+
+public class HalfHourBlocksTest {
+
+ @Test
+ public void createHalfHourBlocks() {
+ HalfHourBlocks halfHourBlocks = new HalfHourBlocks(16, 32);
+
+ // Test creation of HalfHourBlocks
+ assertTrue(halfHourBlocks.overlaps(new HalfHourBlocks(0, 48)));
+ assertFalse(halfHourBlocks.overlaps(new HalfHourBlocks(0, 16)));
+ }
+
+ @Test
+ public void compareTo() {
+ HalfHourBlocks halfHourBlocks1 = new HalfHourBlocks(16, 32);
+ HalfHourBlocks halfHourBlocks2 = new HalfHourBlocks(24, 40);
+ HalfHourBlocks halfHourBlocks3 = new HalfHourBlocks(0, 8);
+
+ // Test compareTo
+ assertTrue(halfHourBlocks1.compareTo(halfHourBlocks2) < 0);
+ assertTrue(halfHourBlocks2.compareTo(halfHourBlocks1) > 0);
+ assertTrue(halfHourBlocks1.compareTo(halfHourBlocks3) > 0);
+ }
+
+ @Test
+ public void overlaps() {
+ HalfHourBlocks halfHourBlocks1 = new HalfHourBlocks(16, 32);
+ HalfHourBlocks halfHourBlocks2 = new HalfHourBlocks(24, 40);
+ HalfHourBlocks halfHourBlocks3 = new HalfHourBlocks(0, 8);
+
+ // Test overlaps method
+ assertTrue(halfHourBlocks1.overlaps(halfHourBlocks2));
+ assertFalse(halfHourBlocks1.overlaps(halfHourBlocks3));
+ }
+
+ @Test
+ public void getOverlap() {
+ HalfHourBlocks halfHourBlocks1 = new HalfHourBlocks(16, 32);
+ HalfHourBlocks halfHourBlocks2 = new HalfHourBlocks(24, 40);
+
+ // Test getOverlap method
+ HalfHourBlocks overlap = halfHourBlocks1.getOverlap(halfHourBlocks2);
+ assertTrue(overlap.overlaps(halfHourBlocks1));
+ assertTrue(overlap.overlaps(halfHourBlocks2));
+ }
+
+ @Test
+ public void equals() {
+ HalfHourBlocks halfHourBlocks1 = new HalfHourBlocks(8, 20);
+ HalfHourBlocks halfHourBlocks2 = new HalfHourBlocks(8, 20);
+ HalfHourBlocks halfHourBlocks3 = new HalfHourBlocks(12, 24);
+
+ // Test equals method
+ assertTrue(halfHourBlocks1.equals(halfHourBlocks1));
+ assertTrue(halfHourBlocks1.equals(halfHourBlocks2));
+ assertFalse(halfHourBlocks1.equals(halfHourBlocks3));
+ assertFalse(halfHourBlocks1.equals(null));
+ }
+
+ @Test
+ public void testHashCode() {
+ HalfHourBlocks halfHourBlocks1 = new HalfHourBlocks(8, 20);
+ HalfHourBlocks halfHourBlocks2 = new HalfHourBlocks(8, 20);
+ HalfHourBlocks halfHourBlocks3 = new HalfHourBlocks(12, 24);
+
+ // Test hashCode method
+ assertEquals(halfHourBlocks1.hashCode(), halfHourBlocks2.hashCode());
+ assertNotEquals(halfHourBlocks1.hashCode(), halfHourBlocks3.hashCode());
+ }
+
+ @Test
+ public void testToString() {
+ HalfHourBlocks halfHourBlocks = new HalfHourBlocks(8, 20);
+
+ // Test toString method
+ assertEquals("0400 1000", halfHourBlocks.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/timetable/ModuleTest.java b/src/test/java/seedu/address/model/person/timetable/ModuleTest.java
new file mode 100644
index 00000000000..52da4bd7111
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/timetable/ModuleTest.java
@@ -0,0 +1,120 @@
+package seedu.address.model.person.timetable;
+
+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.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+
+public class ModuleTest {
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Module(null, "monday 1200 1400"));
+ }
+
+ @Test
+ public void constructor_invalidName_throwsIllegalArgumentException() {
+ String invalidName = "";
+ assertThrows(IllegalArgumentException.class, () -> new Module(invalidName, "monday 1200 1400"));
+ }
+
+ @Test
+ public void newModule_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> Module.newModule(null));
+ }
+
+ @Test
+ public void newModule_invalidInput_throwsIllegalArgumentException() {
+ String invalidInput = "monday 1200 1400";
+ assertThrows(IllegalArgumentException.class, () -> Module.newModule(invalidInput));
+ }
+
+ @Test
+ public void newModule_invalidName_throwsIllegalValueException() {
+ String invalidInput = "@ monday 1200 1400";
+ assertThrows(IllegalValueException.class, () -> Module.newModule(invalidInput));
+ }
+
+ @Test
+ public void newModule_validInput_success() throws IllegalValueException {
+ String validInput = "CS2103 monday 1200 1400";
+ assertEquals(new Module("CS2103", "monday 1200 1400"), Module.newModule(validInput));
+ }
+
+ @Test
+ public void editName_success() {
+ Module module = new Module("CS2103", "monday 1200 1400");
+ assertEquals(new Module("CS2101", "monday 1200 1400"), module.editName("CS2101"));
+ }
+
+ @Test void editName_failure() {
+ Module module = new Module("CS2103", "monday 1200 1400");
+ assertThrows(IllegalArgumentException.class, () -> module.editName(""));
+ }
+
+ @Test
+ public void isModule_success() {
+ Module module = new Module("CS2103", "monday 1200 1400");
+ assertTrue(module.isModule());
+ }
+
+ @Test
+ public void getType_success() {
+ Module module = new Module("CS2103", "monday 1200 1400");
+ assertEquals("Module", module.getType());
+ }
+
+ @Test
+ public void isValidModuleName() {
+ // null name
+ assertThrows(NullPointerException.class, () -> Module.isValidModuleName(null));
+
+ // invalid name
+ assertFalse(Module.isValidModuleName("")); // empty string
+ assertFalse(Module.isValidModuleName(" ")); // spaces only
+ assertFalse(Module.isValidModuleName("^")); // only non-alphanumeric characters
+ assertFalse(Module.isValidModuleName("CS21*3")); // contains non-alphanumeric characters
+ assertFalse(Module.isValidModuleName("CS2103 CS2103")); // contains spaces
+ assertFalse(Module.isValidModuleName("CS21031")); // more numbers
+ assertFalse(Module.isValidModuleName("CS")); // only alphbets
+
+ // valid name
+ assertTrue(Module.isValidModuleName("CS210")); // 3 numbers
+ assertTrue(Module.isValidModuleName("CSS210")); // 3 starting alphabets
+ assertTrue(Module.isValidModuleName("CS2101S")); // less numbers
+ assertTrue(Module.isValidModuleName("CS2103")); // alphanumeric characters
+ assertTrue(Module.isValidModuleName("cs2103")); // with small letters
+ }
+
+ @Test
+ public void equals() {
+ Module module = new Module("CS2100", "monday 1200 1400");
+
+ // same values -> returns true
+ assertTrue(module.equals(new Module("CS2100", "monday 1200 1400")));
+
+ // same object -> returns true
+ assertTrue(module.equals(module));
+
+ // null -> returns false
+ assertFalse(module.equals(null));
+
+ // different types -> returns false
+ assertFalse(module.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(module.equals(new Cca("CS2101", "monday 1200 1400")));
+
+ assertFalse(module.equals(new Cca("CS2100", "tuesday 1200 1400")));
+ }
+
+ @Test
+ public void testHashCode() {
+ Module module = new Module("CS2103", "monday 1200 1400");
+ assertEquals(module.hashCode(), new Module("CS2103", "monday 1200 1400").hashCode());
+ }
+
+}
diff --git a/src/test/java/seedu/address/model/person/timetable/ScheduleTest.java b/src/test/java/seedu/address/model/person/timetable/ScheduleTest.java
new file mode 100644
index 00000000000..94f5dea9dbd
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/timetable/ScheduleTest.java
@@ -0,0 +1,193 @@
+package seedu.address.model.person.timetable;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.logic.commands.exceptions.CommandException;
+
+public class ScheduleTest {
+
+ @Test
+ public void getScheduleForDayOfWeek_success() {
+ Schedule schedule = new Schedule();
+ schedule.addCca(new Cca("Basketball", "monday 1200 1300"));
+ schedule.addModule(new Module("CS2103", "monday 1400 1500"));
+ List expected = new ArrayList<>();
+ expected.add(new Cca("Basketball", "monday 1200 1300"));
+ expected.add(new Module("CS2103", "monday 1400 1500"));
+ assertEquals(schedule.getScheduleForDayOfWeek(1), expected);
+ }
+
+ @Test
+ public void addModule_success() {
+ Schedule schedule = new Schedule();
+ schedule.addModule(new Module("CS2103", "monday 1400 1500"));
+ List expected = new ArrayList<>();
+ expected.add(new Module("CS2103", "monday 1400 1500"));
+ assertEquals(schedule.getModulesList(), expected);
+ }
+
+ @Test
+ public void addModule_failure() {
+ Schedule schedule = new Schedule();
+ assertThrows(IllegalValueException.class, () -> schedule.addModule("CS2 monday 1400 1500"));
+ }
+
+ @Test
+ public void editModule_success() throws IllegalValueException {
+ Schedule schedule = new Schedule();
+ schedule.addModule(new Module("CS2103", "monday 1400 1500"));
+ schedule.addModule(new Module("CS2101", "monday 1900 2000"));
+ schedule.editModule("CS2103", "CS2100 monday 1600 1700");
+ List expected = new ArrayList<>();
+ expected.add(new Module("CS2101", "monday 1900 2000"));
+ expected.add(new Module("CS2100", "monday 1600 1700"));
+ assertEquals(schedule.getModulesList(), expected);
+ }
+
+ @Test
+ public void editModule_failure() {
+ Schedule schedule = new Schedule();
+ schedule.addModule(new Module("CS2103", "monday 1400 1500"));
+ assertThrows(IllegalArgumentException.class, () -> schedule.editModule("CS2",
+ "monday 1600 1400"));
+ }
+
+ @Test
+ public void deleteModule_success() throws CommandException {
+ Schedule schedule = new Schedule();
+ schedule.addModule(new Module("CS2103", "monday 1400 1500"));
+ schedule.addModule(new Module("CS2101", "monday 1600 1700"));
+ schedule.deleteModule("CS2103");
+ List expected = new ArrayList<>();
+ expected.add(new Module("CS2101", "monday 1600 1700"));
+ assertEquals(schedule.getModulesList(), expected);
+ }
+
+ @Test
+ public void deleteModule_failure() {
+ Schedule schedule = new Schedule();
+ schedule.addModule(new Module("CS2103", "monday 1400 1500"));
+ schedule.addModule(new Module("CS2101", "monday 1600 1700"));
+ assertThrows(CommandException.class, () -> schedule.deleteModule("CS2100"));
+ }
+
+ @Test
+ public void addCca_success() {
+ Schedule schedule = new Schedule();
+ schedule.addCca(new Cca("Basketball", "monday 1400 1500"));
+ List expected = new ArrayList<>();
+ expected.add(new Cca("Basketball", "monday 1400 1500"));
+ assertEquals(schedule.getCcasList(), expected);
+ }
+
+ @Test
+ public void addCca_failure() {
+ Schedule schedule = new Schedule();
+ assertThrows(IllegalValueException.class, () -> schedule.addCca("Basketball* monday 1400 1500"));
+ }
+
+ @Test
+ public void editCca_success() throws IllegalValueException {
+ Schedule schedule = new Schedule();
+ schedule.addCca(new Cca("Basketball", "monday 1400 1500"));
+ schedule.addCca(new Cca("Football", "monday 1900 2000"));
+ schedule.editCca("Basketball", "Tennis monday 1600 1700");
+ List expected = new ArrayList<>();
+ expected.add(new Cca("Football", "monday 1900 2000"));
+ expected.add(new Cca("Tennis", "monday 1600 1700"));
+ assertEquals(schedule.getCcasList(), expected);
+ }
+
+ @Test
+ public void editCca_failure() {
+ Schedule schedule = new Schedule();
+ schedule.addCca(new Cca("Baketball", "monday 1400 1500"));
+ assertThrows(IllegalArgumentException.class, () -> schedule.editCca("Basketball*",
+ "monday 1600 1400"));
+ }
+
+ @Test
+ public void deleteCca_success() throws CommandException {
+ Schedule schedule = new Schedule();
+ schedule.addCca(new Cca("Basketball", "monday 1400 1500"));
+ schedule.addCca(new Cca("Football", "monday 1600 1700"));
+ schedule.deleteCca("Basketball");
+ List expected = new ArrayList<>();
+ expected.add(new Cca("Football", "monday 1600 1700"));
+ assertEquals(schedule.getCcasList(), expected);
+ }
+
+ @Test
+ public void deleteCca_failure() {
+ Schedule schedule = new Schedule();
+ schedule.addCca(new Cca("Basketball", "monday 1400 1500"));
+ schedule.addCca(new Cca("Football", "monday 1600 1700"));
+ assertThrows(CommandException.class, () -> schedule.deleteCca("CS2100"));
+ }
+
+ @Test
+ public void addDatedEvent_success() {
+ Schedule schedule = new Schedule();
+ schedule.addDatedEvent("Walk Dog 2023-10-30 1400 1500 y");
+ List expected = new ArrayList<>();
+ expected.add(new DatedEvent("Walk Dog", "monday 1400 1500",
+ "2023-10-30", true));
+ assertEquals(schedule.getDatedEventsList(), expected);
+ }
+
+ @Test
+ public void editDatedEvent_success() {
+ Schedule schedule = new Schedule();
+ schedule.addDatedEvent("Walk Dog 2023-10-30 1400 1500 y");
+ schedule.addDatedEvent("Sleep 2023-10-31 1400 1500 y");
+ schedule.editDatedEvent("Walk Dog", "Walk Cat 2023-10-30 1400 1500 y");
+ List expected = new ArrayList<>();
+ expected.add(new DatedEvent("Sleep", "tuesday 1400 1500",
+ "2023-10-31", true));
+ expected.add(new DatedEvent("Walk Cat", "monday 1400 1500",
+ "2023-10-30", true));
+ assertEquals(schedule.getDatedEventsList(), expected);
+ }
+
+ @Test
+ public void deleteDatedEvent_success() throws Exception {
+ Schedule schedule = new Schedule();
+ schedule.addDatedEvent("Walk Dog 2023-10-30 1400 1500 y");
+ schedule.addDatedEvent("Sleep 2023-10-31 1400 1500 y");
+ schedule.deleteDatedEvent("Walk Dog");
+ List expected = new ArrayList<>();
+ expected.add(new DatedEvent("Sleep", "tuesday 1400 1500",
+ "2023-10-31", true));
+ assertEquals(schedule.getDatedEventsList(), expected);
+ }
+
+ @Test
+ public void deleteDatedEvent_failure() {
+ Schedule schedule = new Schedule();
+ schedule.addDatedEvent("Walk Dog 2023-10-30 1400 1500 y");
+ schedule.addDatedEvent("Sleep 2023-10-31 1400 1500 y");
+ assertThrows(Exception.class, () -> schedule.deleteDatedEvent("CS2100"));
+ }
+
+ @Test
+ public void equals() {
+ Schedule schedule = new Schedule();
+ schedule.addCca(new Cca("Basketball", "monday 1400 1500"));
+ schedule.addDatedEvent("Sleep 2023-10-31 1400 1500 y");
+ Schedule schedule2 = new Schedule();
+
+ assertEquals(schedule, schedule);
+ assertNotEquals(null, schedule);
+ assertNotEquals(schedule, schedule2);
+ }
+
+
+}
diff --git a/src/test/java/seedu/address/model/person/timetable/TimeBlockTest.java b/src/test/java/seedu/address/model/person/timetable/TimeBlockTest.java
new file mode 100644
index 00000000000..15f7ad6c949
--- /dev/null
+++ b/src/test/java/seedu/address/model/person/timetable/TimeBlockTest.java
@@ -0,0 +1,117 @@
+package seedu.address.model.person.timetable;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class TimeBlockTest {
+
+ @Test
+ public void validTimeBlockCreation() {
+ String validTimeBlockString = "Monday 0800 1000";
+ assertDoesNotThrow(() -> new Cca("Sleep", validTimeBlockString));
+ }
+
+ @Test
+ public void invalidTimeBlockCreation() {
+ String invalidTimeBlockString = "InvalidTimeBlockString";
+ assertThrows(IllegalArgumentException.class, () -> new Cca("Sleep",
+ invalidTimeBlockString));
+ }
+
+ @Test
+ public void isValidTimeBlockValid() {
+ String validTimeBlockString = "Monday 0800 1000";
+ assertTrue(Cca.isValidTimeBlock(validTimeBlockString));
+ }
+
+ @Test
+ public void isValidTimeBlockInvalid() {
+ String invalidTimeBlockString = "InvalidTimeBlockString";
+ assertFalse(Cca.isValidTimeBlock(invalidTimeBlockString));
+ }
+
+ @Test
+ public void compareTimeBlocks() {
+ TimeBlock timeBlock1 = new Cca("sleep", "Monday 0900 1100");
+ TimeBlock timeBlock2 = new Cca("sleep", "Monday 1200 1300");
+ assertTrue(timeBlock1.compareTo(timeBlock2) < 0);
+ }
+
+ @Test
+ public void compareTimeBlocksByStartTime() {
+ TimeBlock timeBlock1 = new Cca("sleep", "Monday 0800 1000");
+ TimeBlock timeBlock2 = new Cca("sleep", "Monday 0900 1100");
+ assertTrue(timeBlock1.compareByStartTime(timeBlock2) < 0);
+ }
+
+ @Test
+ public void compareTimeBlocksByDay() {
+ TimeBlock timeBlock1 = new Cca("sleep", "Monday 0900 1100");
+ TimeBlock timeBlock2 = new Cca("sleep", "Tuesday 1200 1300");
+ assertTrue(timeBlock1.compareTo(timeBlock2) < 0);
+ }
+
+ @Test
+ public void overlappingTimeBlocks() {
+ TimeBlock timeBlock1 = new Cca("sleep", "Monday 0800 1000");
+ TimeBlock timeBlock2 = new Cca("sleep", "Monday 0900 1100");
+ assertTrue(timeBlock1.isOverlap(timeBlock2));
+ }
+
+ @Test
+ public void nonOverlappingTimeBlocks() {
+ TimeBlock timeBlock1 = new Cca("sleep", "Monday 0800 1000");
+ TimeBlock timeBlock2 = new Cca("sleep", "Monday 1200 1300");
+ TimeBlock timeBlock3 = new Cca("sleep", "Tuesday 0800 1000");
+ assertFalse(timeBlock1.isOverlap(timeBlock2));
+ assertFalse(timeBlock1.isOverlap(timeBlock3));
+ }
+
+ @Test
+ public void isVarious() {
+ TimeBlock timeBlock1 = new Cca("sleep", "Monday 0800 1000");
+ TimeBlock timeBlock2 = new Module("CS2103", "Monday 0800 1000");
+ assertFalse(timeBlock1.isModule());
+ assertFalse(timeBlock1.isFreeTime());
+ assertFalse(timeBlock1.isDatedEvent());
+ assertFalse(timeBlock2.isCca());
+ }
+
+ @Test
+ public void getStartTime() {
+ TimeBlock timeBlock = new Cca("sleep", "Monday 0800 1000");
+ assertEquals("0800", timeBlock.getStartTime());
+ }
+
+ @Test
+ public void getEndTime() {
+ TimeBlock timeBlock = new Cca("sleep", "Monday 0800 1000");
+ assertEquals("1000", timeBlock.getEndTime());
+ }
+
+ @Test
+ public void isOnDayMatching() {
+ TimeBlock timeBlock = new Cca("sleep", "Monday 0800 1000");
+ assertTrue(timeBlock.isOnDay(1)); // Monday is represented as 1
+ }
+
+ @Test
+ public void isOnDayNotMatching() {
+ TimeBlock timeBlock = new Cca("sleep", "Monday 0800 1000");
+ assertFalse(timeBlock.isOnDay(2)); // Tuesday is not matching
+ }
+
+ @Test
+ public void equalsAndHashCode() {
+ TimeBlock timeBlock1 = new Cca("sleep", "Monday 0800 1000");
+ TimeBlock timeBlock2 = new Cca("sleep", "Monday 0800 1000");
+ assertEquals(timeBlock1, timeBlock2);
+ assertEquals(timeBlock1.hashCode(), timeBlock2.hashCode());
+ }
+}
+
diff --git a/src/test/java/seedu/address/model/tag/TagTest.java b/src/test/java/seedu/address/model/tag/TagTest.java
index 64d07d79ee2..b88b7b63ee7 100644
--- a/src/test/java/seedu/address/model/tag/TagTest.java
+++ b/src/test/java/seedu/address/model/tag/TagTest.java
@@ -1,5 +1,8 @@
package seedu.address.model.tag;
+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.testutil.Assert.assertThrows;
import org.junit.jupiter.api.Test;
@@ -23,4 +26,43 @@ public void isValidTagName() {
assertThrows(NullPointerException.class, () -> Tag.isValidTagName(null));
}
+ @Test
+ public void equals() {
+ Tag tag1 = new Tag("Friends");
+ Tag tag2 = new Tag("Friends");
+ Tag tag3 = new Tag("Family");
+
+ // Test with the same tags
+ assertTrue(tag1.equals(tag1));
+
+ // Test with different instances but the same name
+ assertTrue(tag1.equals(tag2));
+
+ // Test with different names
+ assertFalse(tag1.equals(tag3));
+
+ // Test with null
+ assertFalse(tag1.equals(null));
+
+ // Test with a different class
+ assertFalse(tag1.equals("Friends"));
+ }
+
+ @Test
+ public void testHashCode() {
+ Tag tag1 = new Tag("Friends");
+ Tag tag2 = new Tag("Friends");
+
+ // Hash code should be the same for equal objects
+ assertEquals(tag1.hashCode(), tag2.hashCode());
+ }
+
+ @Test
+ public void testToString() {
+ Tag tag = new Tag("Friends");
+
+ // Check the string representation
+ assertEquals("[Friends]", tag.toString());
+ }
+
}
diff --git a/src/test/java/seedu/address/model/user/UserDataTest.java b/src/test/java/seedu/address/model/user/UserDataTest.java
new file mode 100644
index 00000000000..880b5632d3e
--- /dev/null
+++ b/src/test/java/seedu/address/model/user/UserDataTest.java
@@ -0,0 +1,60 @@
+package seedu.address.model.user;
+
+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.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.testutil.UserBuilder;
+
+public class UserDataTest {
+ @Test
+ public void setUser_newUser() {
+ UserData userData = new UserData(new UserBuilder().build());
+ assertEquals(userData.getUser(), new UserBuilder().build());
+ }
+
+ @Test
+ public void resetData_null_throwsNullPointerException() {
+ UserData userData = new UserData();
+ assertThrows(NullPointerException.class, () -> userData.resetData(null));
+ }
+
+ @Test
+ public void resetData_filled() {
+ UserData userData = new UserData(new UserBuilder().build());
+ UserData newUserData = new UserData();
+ userData.resetData(newUserData);
+ assertEquals(userData, new UserData());
+ }
+
+ @Test
+ public void equals() {
+ UserData userData = new UserData(new UserBuilder().build());
+
+ // same values -> returns true
+ assertTrue(userData.equals(new UserData(new UserBuilder().build())));
+
+ // same object -> returns true
+ assertTrue(userData.equals(userData));
+
+ // null -> returns false
+ assertFalse(userData.equals(null));
+
+ // different types -> returns false
+ assertFalse(userData.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(userData.equals(new UserData()));
+ }
+
+ @Test
+ public void testToString() {
+ UserData userData = new UserData(new UserBuilder().build());
+ String expected = "User : " + userData.getUser().toString();
+ assertEquals(expected, userData.toString());
+ }
+
+}
diff --git a/src/test/java/seedu/address/model/user/UserPrefsTest.java b/src/test/java/seedu/address/model/user/UserPrefsTest.java
new file mode 100644
index 00000000000..402dac7deba
--- /dev/null
+++ b/src/test/java/seedu/address/model/user/UserPrefsTest.java
@@ -0,0 +1,55 @@
+package seedu.address.model.user;
+
+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.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class UserPrefsTest {
+ @Test
+ public void setGuiSettings_nullGuiSettings_throwsNullPointerException() {
+ UserPrefs userPref = new UserPrefs();
+ assertThrows(NullPointerException.class, () -> userPref.setGuiSettings(null));
+ }
+
+ @Test
+ public void setAddressBookFilePath_nullPath_throwsNullPointerException() {
+ UserPrefs userPrefs = new UserPrefs();
+ assertThrows(NullPointerException.class, () -> userPrefs.setAddressBookFilePath(null));
+ }
+
+ @Test
+ public void equals() {
+ UserPrefs userPrefs = new UserPrefs();
+
+ // same values -> returns true
+ assertTrue(userPrefs.equals(new UserPrefs()));
+
+ // same object -> returns true
+ assertTrue(userPrefs.equals(userPrefs));
+
+ // null -> returns false
+ assertFalse(userPrefs.equals(null));
+
+ // different types -> returns false
+ assertFalse(userPrefs.equals(5.0f));
+
+ }
+
+ @Test
+ public void testHashCode() {
+ UserPrefs userPrefs = new UserPrefs();
+ assertEquals(userPrefs.hashCode(), new UserPrefs().hashCode());
+ }
+
+ @Test
+ public void testToString() {
+ UserPrefs userPrefs = new UserPrefs();
+ String expected = "Gui Settings : " + userPrefs.getGuiSettings()
+ + "\nLocal data file location : " + userPrefs.getAddressBookFilePath();
+ assertEquals(expected, userPrefs.toString());
+ }
+
+}
diff --git a/src/test/java/seedu/address/model/user/UserTest.java b/src/test/java/seedu/address/model/user/UserTest.java
new file mode 100644
index 00000000000..108b24db694
--- /dev/null
+++ b/src/test/java/seedu/address/model/user/UserTest.java
@@ -0,0 +1,32 @@
+package seedu.address.model.user;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.time.LocalDate;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.person.timetable.DatedEvent;
+import seedu.address.testutil.UserBuilder;
+
+public class UserTest {
+
+ @Test
+ public void constructor() {
+ User user = new UserBuilder().build();
+ User expectedUser = new User(user);
+ User expectedUser2 = new User(user, user.getDatedEvents());
+ assertEquals(user, expectedUser);
+ assertEquals(user, expectedUser2);
+ }
+
+ @Test
+ public void returnRemindedDatedEvents() {
+ User user = new UserBuilder().build();
+ user.getSchedule().addDatedEvent(new DatedEvent("Walk Dog", "Monday 1030 1130",
+ LocalDate.now().toString(), true));
+ String expectedMessage = "Walk Dog Monday 1030 1130\n";
+ assertEquals(expectedMessage, user.returnRemindedEvent());
+ }
+
+}
diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
index 83b11331cdb..44fbbd6e557 100644
--- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
+++ b/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
@@ -16,22 +16,28 @@
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Phone;
+import seedu.address.storage.timetable.JsonAdaptedSchedule;
public class JsonAdaptedPersonTest {
private static final String INVALID_NAME = "R@chel";
private static final String INVALID_PHONE = "+651234";
private static final String INVALID_ADDRESS = " ";
private static final String INVALID_EMAIL = "example.com";
+ private static final String INVALID_BIRTHDAY = "2020-02-30";
private static final String INVALID_TAG = "#friend";
+
private static final String VALID_NAME = BENSON.getName().toString();
private static final String VALID_PHONE = BENSON.getPhone().toString();
private static final String VALID_EMAIL = BENSON.getEmail().toString();
private static final String VALID_ADDRESS = BENSON.getAddress().toString();
+ private static final String VALID_BIRTHDAY = BENSON.getBirthday().toString();
private static final List VALID_TAGS = BENSON.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList());
+ private static final JsonAdaptedSchedule VALID_SCHEDULE = new JsonAdaptedSchedule(BENSON.getSchedule());
+
@Test
public void toModelType_validPersonDetails_returnsPerson() throws Exception {
JsonAdaptedPerson person = new JsonAdaptedPerson(BENSON);
@@ -41,14 +47,17 @@ public void toModelType_validPersonDetails_returnsPerson() throws Exception {
@Test
public void toModelType_invalidName_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL,
+ VALID_ADDRESS, VALID_BIRTHDAY, VALID_SCHEDULE, VALID_TAGS);
String expectedMessage = Name.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullName_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL,
+ VALID_ADDRESS, VALID_BIRTHDAY, VALID_SCHEDULE, VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -56,14 +65,17 @@ public void toModelType_nullName_throwsIllegalValueException() {
@Test
public void toModelType_invalidPhone_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL,
+ VALID_ADDRESS, VALID_BIRTHDAY, VALID_SCHEDULE, VALID_TAGS);
String expectedMessage = Phone.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullPhone_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL,
+ VALID_ADDRESS, VALID_BIRTHDAY, VALID_SCHEDULE, VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -71,14 +83,16 @@ public void toModelType_nullPhone_throwsIllegalValueException() {
@Test
public void toModelType_invalidEmail_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_BIRTHDAY,
+ VALID_SCHEDULE, VALID_TAGS);
String expectedMessage = Email.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullEmail_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_BIRTHDAY,
+ VALID_SCHEDULE, VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -86,24 +100,53 @@ public void toModelType_nullEmail_throwsIllegalValueException() {
@Test
public void toModelType_invalidAddress_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_BIRTHDAY,
+ VALID_SCHEDULE, VALID_TAGS);
String expectedMessage = Address.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullAddress_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null,
+ VALID_BIRTHDAY, VALID_SCHEDULE, VALID_TAGS);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
+ @Test
+ public void toModelType_invalidBirthday_throwsIllegalValueException() {
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, INVALID_BIRTHDAY,
+ VALID_SCHEDULE, VALID_TAGS);
+ String expectedMessage = "Birthday should be in the format of YYYY-MM-DD "
+ + "and should be a valid date.";
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullBirthday_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, null,
+ VALID_SCHEDULE, VALID_TAGS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "Birthday");
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullSchedule_throwsIllegalValueException() {
+ JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_BIRTHDAY, null, VALID_TAGS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "Schedule");
+ assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
+ }
+
@Test
public void toModelType_invalidTags_throwsIllegalValueException() {
List invalidTags = new ArrayList<>(VALID_TAGS);
invalidTags.add(new JsonAdaptedTag(INVALID_TAG));
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_BIRTHDAY,
+ VALID_SCHEDULE, invalidTags);
assertThrows(IllegalValueException.class, person::toModelType);
}
diff --git a/src/test/java/seedu/address/storage/JsonAdaptedUserTest.java b/src/test/java/seedu/address/storage/JsonAdaptedUserTest.java
new file mode 100644
index 00000000000..84ff8346bef
--- /dev/null
+++ b/src/test/java/seedu/address/storage/JsonAdaptedUserTest.java
@@ -0,0 +1,157 @@
+package seedu.address.storage;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.address.storage.JsonAdaptedUser.MISSING_FIELD_MESSAGE_FORMAT;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalUsers.JOHN;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Phone;
+import seedu.address.storage.timetable.JsonAdaptedDatedEvent;
+import seedu.address.storage.timetable.JsonAdaptedSchedule;
+
+public class JsonAdaptedUserTest {
+ private static final String INVALID_NAME = "R@chel";
+ private static final String INVALID_PHONE = "+651234";
+ private static final String INVALID_ADDRESS = " ";
+ private static final String INVALID_EMAIL = "example.com";
+ private static final String INVALID_BIRTHDAY = "2020-02-30";
+ private static final String INVALID_TAG = "#friend";
+
+
+ private static final String VALID_NAME = JOHN.getName().toString();
+ private static final String VALID_PHONE = JOHN.getPhone().toString();
+ private static final String VALID_EMAIL = JOHN.getEmail().toString();
+ private static final String VALID_ADDRESS = JOHN.getAddress().toString();
+ private static final String VALID_BIRTHDAY = JOHN.getBirthday().toString();
+ private static final List VALID_TAGS = JOHN.getTags().stream()
+ .map(JsonAdaptedTag::new)
+ .collect(Collectors.toList());
+
+ private static final List VALID_DATEDEVENTS = JOHN.getDatedEvents().stream()
+ .map(JsonAdaptedDatedEvent::new)
+ .collect(Collectors.toList());
+
+ private static final JsonAdaptedSchedule VALID_SCHEDULE = new JsonAdaptedSchedule(JOHN.getSchedule());
+
+ @Test
+ public void toModelType_validUserDetails_returnsUser() throws Exception {
+ JsonAdaptedUser user = new JsonAdaptedUser(JOHN);
+ assertEquals(JOHN, user.toModelType());
+ }
+
+ @Test
+ public void toModelType_invalidName_throwsIllegalValueException() {
+ JsonAdaptedUser user =
+ new JsonAdaptedUser(INVALID_NAME, VALID_PHONE, VALID_EMAIL,
+ VALID_ADDRESS, VALID_BIRTHDAY, VALID_TAGS, VALID_SCHEDULE, VALID_DATEDEVENTS);
+ String expectedMessage = Name.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, user::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullName_throwsIllegalValueException() {
+ JsonAdaptedUser user =
+ new JsonAdaptedUser(null, VALID_PHONE, VALID_EMAIL,
+ VALID_ADDRESS, VALID_BIRTHDAY, VALID_TAGS, VALID_SCHEDULE, VALID_DATEDEVENTS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, user::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidPhone_throwsIllegalValueException() {
+ JsonAdaptedUser user =
+ new JsonAdaptedUser(VALID_NAME, INVALID_PHONE, VALID_EMAIL,
+ VALID_ADDRESS, VALID_BIRTHDAY, VALID_TAGS, VALID_SCHEDULE, VALID_DATEDEVENTS);
+ String expectedMessage = Phone.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, user::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullPhone_throwsIllegalValueException() {
+ JsonAdaptedUser user =
+ new JsonAdaptedUser(VALID_NAME, null, VALID_EMAIL,
+ VALID_ADDRESS, VALID_BIRTHDAY, VALID_TAGS, VALID_SCHEDULE, VALID_DATEDEVENTS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, user::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidEmail_throwsIllegalValueException() {
+ JsonAdaptedUser user =
+ new JsonAdaptedUser(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_BIRTHDAY, VALID_TAGS,
+ VALID_SCHEDULE, VALID_DATEDEVENTS);
+ String expectedMessage = Email.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, user::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullEmail_throwsIllegalValueException() {
+ JsonAdaptedUser user = new JsonAdaptedUser(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_BIRTHDAY,
+ VALID_TAGS, VALID_SCHEDULE, VALID_DATEDEVENTS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, user::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidAddress_throwsIllegalValueException() {
+ JsonAdaptedUser user =
+ new JsonAdaptedUser(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_BIRTHDAY, VALID_TAGS,
+ VALID_SCHEDULE, VALID_DATEDEVENTS);
+ String expectedMessage = Address.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, user::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullAddress_throwsIllegalValueException() {
+ JsonAdaptedUser user = new JsonAdaptedUser(VALID_NAME, VALID_PHONE, VALID_EMAIL, null,
+ VALID_BIRTHDAY, VALID_TAGS, VALID_SCHEDULE, VALID_DATEDEVENTS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, user::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidBirthday_throwsIllegalValueException() {
+ JsonAdaptedUser user =
+ new JsonAdaptedUser(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, INVALID_BIRTHDAY, VALID_TAGS,
+ VALID_SCHEDULE, VALID_DATEDEVENTS);
+ String expectedMessage = "Birthday should be in the format of YYYY-MM-DD "
+ + "and should be a valid date.";
+ assertThrows(IllegalValueException.class, expectedMessage, user::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullBirthday_throwsIllegalValueException() {
+ JsonAdaptedUser user = new JsonAdaptedUser(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, null ,
+ VALID_TAGS, VALID_SCHEDULE, VALID_DATEDEVENTS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "Birthday");
+ assertThrows(IllegalValueException.class, expectedMessage, user::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullSchedule_throwsIllegalValueException() {
+ JsonAdaptedUser user = new JsonAdaptedUser(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS,
+ VALID_BIRTHDAY, VALID_TAGS, null, VALID_DATEDEVENTS);
+ String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, "Schedule");
+ assertThrows(IllegalValueException.class, expectedMessage, user::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidTags_throwsIllegalValueException() {
+ List invalidTags = new ArrayList<>(VALID_TAGS);
+ invalidTags.add(new JsonAdaptedTag(INVALID_TAG));
+ JsonAdaptedPerson person =
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_BIRTHDAY,
+ VALID_SCHEDULE, invalidTags);
+ assertThrows(IllegalValueException.class, person::toModelType);
+ }
+}
diff --git a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java
index 188c9058d20..9c82707166a 100644
--- a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java
+++ b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java
@@ -16,16 +16,16 @@
public class JsonSerializableAddressBookTest {
private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonSerializableAddressBookTest");
- private static final Path TYPICAL_PERSONS_FILE = TEST_DATA_FOLDER.resolve("typicalPersonsAddressBook.json");
private static final Path INVALID_PERSON_FILE = TEST_DATA_FOLDER.resolve("invalidPersonAddressBook.json");
private static final Path DUPLICATE_PERSON_FILE = TEST_DATA_FOLDER.resolve("duplicatePersonAddressBook.json");
+ private static final Path TYPICAL_PERSONS_FILE = TEST_DATA_FOLDER.resolve("smallTypicalPersonsAddressBook.json");
@Test
public void toModelType_typicalPersonsFile_success() throws Exception {
JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(TYPICAL_PERSONS_FILE,
JsonSerializableAddressBook.class).get();
AddressBook addressBookFromFile = dataFromFile.toModelType();
- AddressBook typicalPersonsAddressBook = TypicalPersons.getTypicalAddressBook();
+ AddressBook typicalPersonsAddressBook = TypicalPersons.getSmallTypicalAddressBook();
assertEquals(addressBookFromFile, typicalPersonsAddressBook);
}
diff --git a/src/test/java/seedu/address/storage/JsonSerializableUserDataTest.java b/src/test/java/seedu/address/storage/JsonSerializableUserDataTest.java
new file mode 100644
index 00000000000..7ca2ec71d5f
--- /dev/null
+++ b/src/test/java/seedu/address/storage/JsonSerializableUserDataTest.java
@@ -0,0 +1,35 @@
+package seedu.address.storage;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.user.ReadOnlyUserData;
+import seedu.address.model.user.UserData;
+import seedu.address.testutil.UserBuilder;
+
+public class JsonSerializableUserDataTest {
+ @Test
+ public void constructor_validJsonAdaptedPerson_success() throws IllegalValueException {
+ JsonAdaptedUser jsonAdaptedUser = new JsonAdaptedUser(new UserBuilder().build());
+ JsonSerializableUserData userData = new JsonSerializableUserData(jsonAdaptedUser);
+ assertEquals(new UserBuilder().build(), userData.toModelType().getUser());
+ }
+
+ @Test
+ public void constructor_validReadOnlyUserData_success() throws IllegalValueException {
+ ReadOnlyUserData readOnlyUserData = new UserData(new UserBuilder().build());
+ JsonSerializableUserData userData = new JsonSerializableUserData(readOnlyUserData);
+ assertEquals(new UserBuilder().build(), userData.toModelType().getUser());
+ }
+
+ @Test
+ public void toModelType_validJsonSerializableUserData_success() throws IllegalValueException {
+ JsonAdaptedUser jsonAdaptedUser = new JsonAdaptedUser(new UserBuilder().build());
+ JsonSerializableUserData userData = new JsonSerializableUserData(jsonAdaptedUser);
+ UserData modelUserData = userData.toModelType();
+ assertEquals(new UserBuilder().build(), modelUserData.getUser());
+ }
+
+}
diff --git a/src/test/java/seedu/address/storage/JsonUserDataStorageTest.java b/src/test/java/seedu/address/storage/JsonUserDataStorageTest.java
new file mode 100644
index 00000000000..224c7e20355
--- /dev/null
+++ b/src/test/java/seedu/address/storage/JsonUserDataStorageTest.java
@@ -0,0 +1,121 @@
+package seedu.address.storage;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import seedu.address.commons.exceptions.DataLoadingException;
+import seedu.address.model.user.ReadOnlyUserData;
+import seedu.address.model.user.UserData;
+import seedu.address.testutil.UserBuilder;
+
+public class JsonUserDataStorageTest {
+ private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonUserDataStorageTest");
+
+ @TempDir
+ public Path testFolder;
+
+ @Test
+ public void constructor_validPath_success() {
+ Path filePath = Paths.get("TypicalUserData.json");
+ JsonUserDataStorage userDataStorage = new JsonUserDataStorage(filePath);
+ assertEquals(filePath, userDataStorage.getUserDataFilePath());
+ }
+
+ @Test
+ public void readUserData_nullFilePath_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> readUserData(null));
+ }
+
+ public Optional readUserData(String userDataFileInTestDataFolder) throws DataLoadingException {
+ Path userDataFilePath = addToTestDataPathIfNotNull(userDataFileInTestDataFolder);
+ return new JsonUserDataStorage(userDataFilePath).readUserData(userDataFilePath);
+ }
+
+ private Path addToTestDataPathIfNotNull(String userDataFileInTestDataFolder) {
+ return userDataFileInTestDataFolder != null
+ ? TEST_DATA_FOLDER.resolve(userDataFileInTestDataFolder)
+ : null;
+ }
+
+ @Test
+ public void readUserData_missingFile_emptyResult() throws DataLoadingException {
+ assertFalse(readUserData("NonExistentFile.json").isPresent());
+ }
+
+ @Test
+ public void readUserData_notJsonFormat_exceptionThrown() {
+ assertThrows(DataLoadingException.class, () -> readUserData("NotJsonFormatUserData.json"));
+ }
+
+ @Test
+ public void readUserData_illegalValue_exceptionThrown() {
+ assertThrows(DataLoadingException.class, () -> readUserData("InvalidUserData.json"));
+ }
+
+ @Test
+ public void readUserData_successfullyRead() throws DataLoadingException {
+ ReadOnlyUserData expected = getTypicalUserData();
+ ReadOnlyUserData actual = readUserData("TypicalUserData.json").get();
+ assertEquals(expected, actual);
+ }
+
+ private UserData getTypicalUserData() {
+ UserData userData = new UserData();
+ userData.setUser(new UserBuilder().build());
+ return userData;
+ }
+
+ @Test
+ public void readUserData_extraValuesInFile_extraValuesIgnored() throws DataLoadingException {
+ ReadOnlyUserData expected = getTypicalUserData();
+ ReadOnlyUserData actual = readUserData("ExtraValuesUserData.json").get();
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void saveData_nullData_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> saveUserData(null, "SomeFile.json"));
+ }
+
+ @Test
+ public void saveData_nullFilePath_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> saveUserData(new UserData(), null));
+ }
+
+ private void saveUserData(UserData userData, String dataFileInTestDataFolder) {
+ Path userDataFilePath = addToTestDataPathIfNotNull(dataFileInTestDataFolder);
+ try {
+ new JsonUserDataStorage(userDataFilePath).saveUserData(userData);
+ } catch (IOException ioe) {
+ throw new AssertionError("There should not be an error writing to the file.", ioe);
+ }
+ }
+
+ @Test
+ public void saveData_success() throws DataLoadingException, IOException {
+ UserData original = new UserData();
+ original.setUser(new UserBuilder().build());
+
+ Path dataFilePath = testFolder.resolve("TempData.json");
+ JsonUserDataStorage jsonUserDataStorage = new JsonUserDataStorage(dataFilePath);
+
+ jsonUserDataStorage.saveUserData(original);
+ ReadOnlyUserData readBack = jsonUserDataStorage.readUserData().get();
+ assertEquals(original, readBack);
+
+ original.setUser(new UserBuilder().withName("Bobby").build());
+ jsonUserDataStorage.saveUserData(original);
+ readBack = jsonUserDataStorage.readUserData().get();
+ assertEquals(original, readBack);
+ }
+
+}
diff --git a/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java b/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java
index ed0a413526a..b71372930ac 100644
--- a/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java
+++ b/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java
@@ -14,7 +14,7 @@
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.exceptions.DataLoadingException;
-import seedu.address.model.UserPrefs;
+import seedu.address.model.user.UserPrefs;
public class JsonUserPrefsStorageTest {
@@ -23,6 +23,13 @@ public class JsonUserPrefsStorageTest {
@TempDir
public Path testFolder;
+ @Test
+ public void constructor_validPath_success() {
+ Path filePath = Paths.get("TypicalUserPrefs.json");
+ JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(filePath);
+ assertEquals(filePath, userPrefsStorage.getUserPrefsFilePath());
+ }
+
@Test
public void readUserPrefs_nullFilePath_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> readUserPrefs(null));
diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/address/storage/StorageManagerTest.java
index 99a16548970..65a09ebf571 100644
--- a/src/test/java/seedu/address/storage/StorageManagerTest.java
+++ b/src/test/java/seedu/address/storage/StorageManagerTest.java
@@ -13,7 +13,9 @@
import seedu.address.commons.core.GuiSettings;
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.UserPrefs;
+import seedu.address.model.user.ReadOnlyUserData;
+import seedu.address.model.user.UserData;
+import seedu.address.model.user.UserPrefs;
public class StorageManagerTest {
@@ -26,7 +28,8 @@ public class StorageManagerTest {
public void setUp() {
JsonAddressBookStorage addressBookStorage = new JsonAddressBookStorage(getTempFilePath("ab"));
JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(getTempFilePath("prefs"));
- storageManager = new StorageManager(addressBookStorage, userPrefsStorage);
+ JsonUserDataStorage userDataStorage = new JsonUserDataStorage(getTempFilePath("ud"));
+ storageManager = new StorageManager(addressBookStorage, userPrefsStorage, userDataStorage);
}
private Path getTempFilePath(String fileName) {
@@ -60,9 +63,32 @@ public void addressBookReadSave() throws Exception {
assertEquals(original, new AddressBook(retrieved));
}
+ @Test
+ public void userDataReadSave() throws Exception {
+ /*
+ * Note: This is an integration test that verifies the StorageManager is properly wired to the
+ * {@link JsonUserDataStorage} class.
+ * More extensive testing of UserPref saving/reading is done in {@link JsonUserDataStorageTest} class.
+ */
+ UserData original = new UserData();
+ storageManager.saveUserData(original);
+ ReadOnlyUserData retrieved = storageManager.readUserData().get();
+ assertEquals(original, retrieved);
+ }
+
@Test
public void getAddressBookFilePath() {
assertNotNull(storageManager.getAddressBookFilePath());
}
+ @Test
+ public void getUserDataFilePath() {
+ assertNotNull(storageManager.getUserDataFilePath());
+ }
+
+ @Test
+ public void getUserPrefsFilePath() {
+ assertNotNull(storageManager.getUserPrefsFilePath());
+ }
+
}
diff --git a/src/test/java/seedu/address/storage/timetable/JsonAdaptedCcaTest.java b/src/test/java/seedu/address/storage/timetable/JsonAdaptedCcaTest.java
new file mode 100644
index 00000000000..85da7b42b18
--- /dev/null
+++ b/src/test/java/seedu/address/storage/timetable/JsonAdaptedCcaTest.java
@@ -0,0 +1,46 @@
+package seedu.address.storage.timetable;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static seedu.address.testutil.TypicalSchedule.NORMAL_CCA;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.timetable.Cca;
+import seedu.address.model.person.timetable.TimeBlock;
+
+public class JsonAdaptedCcaTest {
+ private static final String INVALID_TIMEBLOCK = "monday";
+
+
+ private static final String VALID_NAME = "CS2100";
+ private static final String VALID_TIMEBLOCK = "Monday 1200 1300";
+
+ @Test
+ public void toModelType_validCcaDetails_returnsModule() throws Exception {
+ JsonAdaptedCca cca = new JsonAdaptedCca(NORMAL_CCA);
+ assertEquals(NORMAL_CCA, cca.toModelType());
+ }
+
+ @Test
+ public void toModelType_nullCcaName_throwsException() {
+ JsonAdaptedCca cca = new JsonAdaptedCca(null, VALID_TIMEBLOCK);
+ String expectedMessage = Cca.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, cca::toModelType, expectedMessage);
+ }
+
+ @Test
+ public void toModelType_invalidTimeBlock_throwsException() {
+ JsonAdaptedCca cca = new JsonAdaptedCca(VALID_NAME, INVALID_TIMEBLOCK);
+ String expectedMessage = TimeBlock.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalArgumentException.class, cca::toModelType, expectedMessage);
+ }
+
+ @Test
+ public void toModelType_nullTimeBlock_throwsException() {
+ JsonAdaptedCca cca = new JsonAdaptedCca(VALID_NAME, null);
+ String expectedMessage = TimeBlock.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, cca::toModelType, expectedMessage);
+ }
+}
diff --git a/src/test/java/seedu/address/storage/timetable/JsonAdaptedDatedEventTest.java b/src/test/java/seedu/address/storage/timetable/JsonAdaptedDatedEventTest.java
new file mode 100644
index 00000000000..905e96e7886
--- /dev/null
+++ b/src/test/java/seedu/address/storage/timetable/JsonAdaptedDatedEventTest.java
@@ -0,0 +1,53 @@
+package seedu.address.storage.timetable;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalSchedule.NORMAL_DATED_EVENT;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.timetable.DatedEvent;
+
+public class JsonAdaptedDatedEventTest {
+ private static final String INVALID_TIMEBLOCK = "monday";
+ private static final String INVALID_DATE = "26-12-2001";
+ private static final String VALID_NAME = "CS2100";
+ private static final String VALID_TIMEBLOCK = "Monday 1200 1300";
+ private static final String VALID_DATE = "2001-12-26 1200 1400";
+
+ @Test
+ public void toModelType_nullName_throwsIllegalValueException() {
+ JsonAdaptedDatedEvent datedEvent = new JsonAdaptedDatedEvent(null, VALID_TIMEBLOCK, VALID_DATE, false);
+ assertThrows(IllegalValueException.class, datedEvent::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidTimeBlock_throwsIllegalValueException() {
+ JsonAdaptedDatedEvent datedEvent = new JsonAdaptedDatedEvent(VALID_NAME, INVALID_TIMEBLOCK, VALID_DATE, false);
+ String expectedMessage = DatedEvent.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, datedEvent::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullTimeBlock_throwsIllegalValueException() {
+ JsonAdaptedDatedEvent datedEvent = new JsonAdaptedDatedEvent(VALID_NAME, null, VALID_DATE, false);
+ String expectedMessage = DatedEvent.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, datedEvent::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidDateString_throwsIllegalValueException() {
+ JsonAdaptedDatedEvent datedEvent = new JsonAdaptedDatedEvent(VALID_NAME, VALID_TIMEBLOCK, INVALID_DATE, false);
+ String expectedMessage = DatedEvent.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, datedEvent::toModelType);
+ }
+
+ @Test
+ public void toModelType_validDatedEvent_success() throws IllegalValueException {
+ JsonAdaptedDatedEvent datedEvent = new JsonAdaptedDatedEvent(NORMAL_DATED_EVENT);
+ assertEquals(NORMAL_DATED_EVENT, datedEvent.toModelType());
+ }
+
+
+}
diff --git a/src/test/java/seedu/address/storage/timetable/JsonAdaptedModuleTest.java b/src/test/java/seedu/address/storage/timetable/JsonAdaptedModuleTest.java
new file mode 100644
index 00000000000..dda34bd900d
--- /dev/null
+++ b/src/test/java/seedu/address/storage/timetable/JsonAdaptedModuleTest.java
@@ -0,0 +1,54 @@
+package seedu.address.storage.timetable;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static seedu.address.testutil.TypicalSchedule.NORMAL_MODULE;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.person.timetable.Module;
+import seedu.address.model.person.timetable.TimeBlock;
+
+public class JsonAdaptedModuleTest {
+ private static final String INVALID_NAME = "woohoo";
+ private static final String INVALID_TIMEBLOCK = "monday";
+
+
+ private static final String VALID_NAME = "CS2100";
+ private static final String VALID_TIMEBLOCK = "Monday 1200 1300";
+
+ @Test
+ public void toModelType_validModuleDetails_returnsModule() throws Exception {
+ JsonAdaptedModule module = new JsonAdaptedModule(NORMAL_MODULE);
+ assertEquals(NORMAL_MODULE, module.toModelType());
+ }
+
+ @Test
+ public void toModelType_invalidModuleName_throwsException() {
+ JsonAdaptedModule module = new JsonAdaptedModule(INVALID_NAME, VALID_TIMEBLOCK);
+ String expectedMessage = Module.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalArgumentException.class, module::toModelType, expectedMessage);
+ }
+
+ @Test
+ public void toModelType_nullModuleName_throwsException() {
+ JsonAdaptedModule module = new JsonAdaptedModule(null, VALID_TIMEBLOCK);
+ String expectedMessage = Module.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, module::toModelType, expectedMessage);
+ }
+
+ @Test
+ public void toModelType_invalidTimeBlock_throwsException() {
+ JsonAdaptedModule module = new JsonAdaptedModule(VALID_NAME, INVALID_TIMEBLOCK);
+ String expectedMessage = TimeBlock.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalArgumentException.class, module::toModelType, expectedMessage);
+ }
+
+ @Test
+ public void toModelType_nullTimeBlock_throwsException() {
+ JsonAdaptedModule module = new JsonAdaptedModule(VALID_NAME, null);
+ String expectedMessage = TimeBlock.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, module::toModelType, expectedMessage);
+ }
+}
diff --git a/src/test/java/seedu/address/storage/timetable/JsonAdaptedScheduleTest.java b/src/test/java/seedu/address/storage/timetable/JsonAdaptedScheduleTest.java
new file mode 100644
index 00000000000..b895b25a8c0
--- /dev/null
+++ b/src/test/java/seedu/address/storage/timetable/JsonAdaptedScheduleTest.java
@@ -0,0 +1,36 @@
+package seedu.address.storage.timetable;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.person.Person;
+import seedu.address.model.person.timetable.Cca;
+import seedu.address.model.person.timetable.DatedEvent;
+import seedu.address.model.person.timetable.Module;
+import seedu.address.model.person.timetable.Schedule;
+import seedu.address.testutil.PersonBuilder;
+
+
+public class JsonAdaptedScheduleTest {
+ public static final String VALID_MODULE_NAME = "CS2100";
+ public static final String VALID_TIMEBLOCK = "Monday 1200 1300";
+ public static final String VALID_DATE = "2001-12-26";
+
+ public static final String VALID_EVENT_NAME = "Walk Dog";
+
+ public static final boolean VALID_EVENT_REMINDER = true;
+
+ public static final Person VALID_PERSON = new PersonBuilder().build();
+
+ @Test
+ public void toModelType_validSchedule_returnsSchedule() throws Exception {
+ Schedule schedule = new Schedule();
+ schedule.addModule(new Module(VALID_MODULE_NAME, VALID_TIMEBLOCK));
+ schedule.addDatedEvent(new DatedEvent(VALID_EVENT_NAME, VALID_TIMEBLOCK, VALID_DATE, VALID_EVENT_REMINDER));
+ schedule.addCca(new Cca(VALID_MODULE_NAME, VALID_TIMEBLOCK));
+
+ JsonAdaptedSchedule jsonAdaptedSchedule = new JsonAdaptedSchedule(schedule);
+ Schedule schedule1 = jsonAdaptedSchedule.toModelType();
+
+ assert schedule1.equals(schedule);
+ }
+}
diff --git a/src/test/java/seedu/address/testutil/DatedEventsBuilder.java b/src/test/java/seedu/address/testutil/DatedEventsBuilder.java
new file mode 100644
index 00000000000..7c95cff128b
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/DatedEventsBuilder.java
@@ -0,0 +1,45 @@
+package seedu.address.testutil;
+
+import java.util.ArrayList;
+
+import seedu.address.model.person.timetable.DatedEvent;
+
+/**
+ * A utility class to help with building DatedEvents objects.
+ * Example usage:
+ * {@code DatedEvents de = new DatedEventsBuilder().withDatedEvent("CS2103T Lecture").build();}
+ */
+public class DatedEventsBuilder {
+ private ArrayList datedEvents;
+
+ /**
+ * Creates a {@code DatedEventsBuilder} with the default details.
+ */
+ public DatedEventsBuilder(ArrayList datedEvents) {
+ this.datedEvents = datedEvents;
+ }
+
+ /**
+ * Creates a {@code DatedEventsBuilder} with the default details.
+ */
+ public DatedEventsBuilder() {
+ datedEvents = new ArrayList<>();
+ }
+
+ /**
+ * Adds a new {@code DatedEvent} to the {@code DatedEvents} that we are building.
+ */
+ public DatedEventsBuilder withDatedEvent(DatedEvent datedEvent) {
+ datedEvents.add(datedEvent);
+ return this;
+ }
+
+ /**
+ * Builds the DatedEvents object.
+ * @return DatedEvents object.
+ */
+ public ArrayList build() {
+ return datedEvents;
+ }
+
+}
diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
index 4584bd5044e..6b58f8cd7dd 100644
--- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
+++ b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
@@ -4,12 +4,14 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
+import seedu.address.logic.commands.edit.EditPersonDescriptor;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Birthday;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.timetable.Schedule;
import seedu.address.model.tag.Tag;
/**
@@ -17,7 +19,7 @@
*/
public class EditPersonDescriptorBuilder {
- private EditPersonDescriptor descriptor;
+ private final EditPersonDescriptor descriptor;
public EditPersonDescriptorBuilder() {
descriptor = new EditPersonDescriptor();
@@ -36,6 +38,8 @@ public EditPersonDescriptorBuilder(Person person) {
descriptor.setPhone(person.getPhone());
descriptor.setEmail(person.getEmail());
descriptor.setAddress(person.getAddress());
+ descriptor.setBirthday(person.getBirthday());
+ descriptor.setSchedule(person.getSchedule());
descriptor.setTags(person.getTags());
}
@@ -55,6 +59,14 @@ public EditPersonDescriptorBuilder withPhone(String phone) {
return this;
}
+ /**
+ * Sets the {@code Phone} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withPhone(Phone phone) {
+ descriptor.setPhone(phone);
+ return this;
+ }
+
/**
* Sets the {@code Email} of the {@code EditPersonDescriptor} that we are building.
*/
@@ -63,6 +75,14 @@ public EditPersonDescriptorBuilder withEmail(String email) {
return this;
}
+ /**
+ * Sets the {@code Email} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withEmail(Email email) {
+ descriptor.setEmail(email);
+ return this;
+ }
+
/**
* Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building.
*/
@@ -71,6 +91,30 @@ public EditPersonDescriptorBuilder withAddress(String address) {
return this;
}
+ /**
+ * Sets the {@code Birthday} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withBirthday(String birthday) {
+ descriptor.setBirthday(new Birthday(birthday));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Schedule} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withSchedule(Schedule schedule) {
+ descriptor.setSchedule(schedule);
+ return this;
+ }
+
+ /**
+ * Sets the {@code Schedule} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withSchedule() {
+ descriptor.setSchedule(new Schedule());
+ return this;
+ }
+
/**
* Parses the {@code tags} into a {@code Set} and set it to the {@code EditPersonDescriptor}
* that we are building.
diff --git a/src/test/java/seedu/address/testutil/EditUserDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditUserDescriptorBuilder.java
new file mode 100644
index 00000000000..c292b5be3e0
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/EditUserDescriptorBuilder.java
@@ -0,0 +1,151 @@
+package seedu.address.testutil;
+
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.edit.EditUserDescriptor;
+import seedu.address.model.person.Address;
+import seedu.address.model.person.Birthday;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Phone;
+import seedu.address.model.person.timetable.DatedEvent;
+import seedu.address.model.person.timetable.Schedule;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.user.User;
+
+/**
+ * A utility class to help with building EditUserDescriptorBuilder objects.
+ */
+public class EditUserDescriptorBuilder {
+
+ private final EditUserDescriptor descriptor;
+
+ public EditUserDescriptorBuilder() {
+ descriptor = new EditUserDescriptor();
+ }
+
+ public EditUserDescriptorBuilder(EditUserDescriptor descriptor) {
+ this.descriptor = new EditUserDescriptor(descriptor);
+ }
+
+ /**
+ * Returns an {@code EditUserDescriptorBuilder} with fields containing {@code user}'s details
+ */
+ public EditUserDescriptorBuilder(User user) {
+ descriptor = new EditUserDescriptor();
+ descriptor.setName(user.getName());
+ descriptor.setPhone(user.getPhone());
+ descriptor.setEmail(user.getEmail());
+ descriptor.setAddress(user.getAddress());
+ descriptor.setBirthday(user.getBirthday());
+ descriptor.setSchedule(user.getSchedule());
+ descriptor.setTags(user.getTags());
+ descriptor.setDatedEvents(user.getDatedEvents());
+ }
+
+ /**
+ * Sets the {@code Name} of the {@code EditUserDescriptorBuilder} that we are building.
+ */
+ public EditUserDescriptorBuilder withName(String name) {
+ descriptor.setName(new Name(name));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Phone} of the {@code EditUserDescriptorBuilder} that we are building.
+ */
+ public EditUserDescriptorBuilder withPhone(String phone) {
+ descriptor.setPhone(new Phone(phone));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Phone} of the {@code EditUserDescriptorBuilder} that we are building.
+ */
+ public EditUserDescriptorBuilder withPhone(Phone phone) {
+ descriptor.setPhone(phone);
+ return this;
+ }
+
+ /**
+ * Sets the {@code Email} of the {@code EditUserDescriptorBuilder} that we are building.
+ */
+ public EditUserDescriptorBuilder withEmail(String email) {
+ descriptor.setEmail(new Email(email));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Email} of the {@code EditUserDescriptorBuilder} that we are building.
+ */
+ public EditUserDescriptorBuilder withEmail(Email email) {
+ descriptor.setEmail(email);
+ return this;
+ }
+
+ /**
+ * Sets the {@code Address} of the {@code EditUserDescriptorBuilder} that we are building.
+ */
+ public EditUserDescriptorBuilder withAddress(String address) {
+ descriptor.setAddress(new Address(address));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Birthday} of the {@code EditUserDescriptorBuilder} that we are building.
+ */
+ public EditUserDescriptorBuilder withBirthday(String birthday) {
+ descriptor.setBirthday(new Birthday(birthday));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Schedule} of the {@code EditUserDescriptorBuilder} that we are building.
+ */
+ public EditUserDescriptorBuilder withSchedule() {
+ descriptor.setSchedule(new Schedule());
+ return this;
+ }
+
+ /**
+ * Sets the {@code Schedule} of the {@code EditUserDescriptorBuilder} that we are building.
+ */
+ public EditUserDescriptorBuilder withSchedule(Schedule schedule) {
+ descriptor.setSchedule(schedule);
+ return this;
+ }
+
+ /**
+ * Sets the {@code Schedule} of the {@code EditUserDescriptorBuilder} that we are building.
+ */
+ public EditUserDescriptorBuilder withDatedEvents() {
+ descriptor.setDatedEvents(new ArrayList());
+ return this;
+ }
+
+ /**
+ * Sets the {@code Schedule} of the {@code EditUserDescriptorBuilder} that we are building.
+ */
+ public EditUserDescriptorBuilder withDatedEvents(ArrayList datedEvents) {
+ descriptor.setDatedEvents(datedEvents);
+ return this;
+ }
+
+ /**
+ * Parses the {@code tags} into a {@code Set} and set it to the {@code EditUserDescriptorBuilder}
+ * that we are building.
+ */
+ public EditUserDescriptorBuilder withTags(String... tags) {
+ Set tagSet = Stream.of(tags).map(Tag::new).collect(Collectors.toSet());
+ descriptor.setTags(tagSet);
+ return this;
+ }
+
+ public EditUserDescriptor build() {
+ return descriptor;
+ }
+}
+
diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java
index 6be381d39ba..26a8a954a14 100644
--- a/src/test/java/seedu/address/testutil/PersonBuilder.java
+++ b/src/test/java/seedu/address/testutil/PersonBuilder.java
@@ -4,10 +4,12 @@
import java.util.Set;
import seedu.address.model.person.Address;
+import seedu.address.model.person.Birthday;
import seedu.address.model.person.Email;
import seedu.address.model.person.Name;
import seedu.address.model.person.Person;
import seedu.address.model.person.Phone;
+import seedu.address.model.person.timetable.Schedule;
import seedu.address.model.tag.Tag;
import seedu.address.model.util.SampleDataUtil;
@@ -20,12 +22,15 @@ public class PersonBuilder {
public static final String DEFAULT_PHONE = "85355255";
public static final String DEFAULT_EMAIL = "amy@gmail.com";
public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111";
+ public static final String DEFAULT_BIRTHDAY = "2020-12-01";
private Name name;
private Phone phone;
private Email email;
private Address address;
+ private Birthday birthday;
private Set tags;
+ private Schedule schedule;
/**
* Creates a {@code PersonBuilder} with the default details.
@@ -35,6 +40,8 @@ public PersonBuilder() {
phone = new Phone(DEFAULT_PHONE);
email = new Email(DEFAULT_EMAIL);
address = new Address(DEFAULT_ADDRESS);
+ birthday = new Birthday(DEFAULT_BIRTHDAY);
+ schedule = new Schedule();
tags = new HashSet<>();
}
@@ -46,6 +53,8 @@ public PersonBuilder(Person personToCopy) {
phone = personToCopy.getPhone();
email = personToCopy.getEmail();
address = personToCopy.getAddress();
+ birthday = personToCopy.getBirthday();
+ schedule = personToCopy.getSchedule();
tags = new HashSet<>(personToCopy.getTags());
}
@@ -65,6 +74,22 @@ public PersonBuilder withTags(String ... tags) {
return this;
}
+ /**
+ * Parses the {@code freeTimes} into a {@code Set} and set it to the {@code Person} that we are building.
+ */
+ public PersonBuilder withSchedule() {
+ this.schedule = new Schedule();
+ return this;
+ }
+
+ /**
+ * Sets the {@code Schedule} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withSchedule(Schedule schedule) {
+ this.schedule = schedule;
+ return this;
+ }
+
/**
* Sets the {@code Address} of the {@code Person} that we are building.
*/
@@ -89,8 +114,16 @@ public PersonBuilder withEmail(String email) {
return this;
}
+ /**
+ * Sets the {@code Birthday} of the {@code Person} that we are building.
+ */
+ public PersonBuilder withBirthday(String birthday) {
+ this.birthday = new Birthday(birthday);
+ return this;
+ }
+
public Person build() {
- return new Person(name, phone, email, address, tags);
+ return new Person(name, phone, email, address, birthday, schedule, tags);
}
}
diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java
index 90849945183..cad006eb4bf 100644
--- a/src/test/java/seedu/address/testutil/PersonUtil.java
+++ b/src/test/java/seedu/address/testutil/PersonUtil.java
@@ -1,6 +1,7 @@
package seedu.address.testutil;
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_NAME;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
@@ -9,7 +10,7 @@
import java.util.Set;
import seedu.address.logic.commands.AddCommand;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
+import seedu.address.logic.commands.edit.EditPersonDescriptor;
import seedu.address.model.person.Person;
import seedu.address.model.tag.Tag;
@@ -34,6 +35,7 @@ public static String getPersonDetails(Person person) {
sb.append(PREFIX_PHONE + person.getPhone().value + " ");
sb.append(PREFIX_EMAIL + person.getEmail().value + " ");
sb.append(PREFIX_ADDRESS + person.getAddress().value + " ");
+ sb.append(PREFIX_BIRTHDAY + person.getBirthday().date + " ");
person.getTags().stream().forEach(
s -> sb.append(PREFIX_TAG + s.tagName + " ")
);
@@ -49,6 +51,7 @@ public static String getEditPersonDescriptorDetails(EditPersonDescriptor descrip
descriptor.getPhone().ifPresent(phone -> sb.append(PREFIX_PHONE).append(phone.value).append(" "));
descriptor.getEmail().ifPresent(email -> sb.append(PREFIX_EMAIL).append(email.value).append(" "));
descriptor.getAddress().ifPresent(address -> sb.append(PREFIX_ADDRESS).append(address.value).append(" "));
+ descriptor.getBirthday().ifPresent(birthday -> sb.append(PREFIX_BIRTHDAY).append(birthday).append(" "));
if (descriptor.getTags().isPresent()) {
Set tags = descriptor.getTags().get();
if (tags.isEmpty()) {
diff --git a/src/test/java/seedu/address/testutil/ScheduleBuilder.java b/src/test/java/seedu/address/testutil/ScheduleBuilder.java
new file mode 100644
index 00000000000..df7f9bc9a68
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/ScheduleBuilder.java
@@ -0,0 +1,53 @@
+package seedu.address.testutil;
+
+import seedu.address.model.person.timetable.Cca;
+import seedu.address.model.person.timetable.DatedEvent;
+import seedu.address.model.person.timetable.Module;
+import seedu.address.model.person.timetable.Schedule;
+
+/**
+ * A utility class to help with building Schedule objects.
+ * Example usage:
+ * {@code Schedule ab = new ScheduleBuilder().withModule("CS2103T").build();}
+ */
+public class ScheduleBuilder {
+
+ private Schedule schedule;
+
+ public ScheduleBuilder() {
+ schedule = new Schedule();
+ }
+
+ public ScheduleBuilder(Schedule schedule) {
+ this.schedule = schedule;
+ }
+
+ /**
+ * Adds a new {@code Module} to the {@code Schedule} that we are building.
+ */
+ public ScheduleBuilder withModule(Module module) {
+ schedule.addModule(module);
+ return this;
+ }
+
+ /**
+ * Adds a new {@code Cca} to the {@code Schedule} that we are building.
+ */
+ public ScheduleBuilder withCca(Cca cca) {
+ schedule.addCca(cca);
+ return this;
+ }
+
+ /**
+ * Adds a new {@code DatedEvent} to the {@code Schedule} that we are building.
+ */
+ public ScheduleBuilder withDatedEvent(DatedEvent datedEvent) {
+ schedule.addDatedEvent(datedEvent);
+ return this;
+ }
+
+
+ public Schedule build() {
+ return schedule;
+ }
+}
diff --git a/src/test/java/seedu/address/testutil/TypicalDatedEvents.java b/src/test/java/seedu/address/testutil/TypicalDatedEvents.java
new file mode 100644
index 00000000000..d59d555a83d
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/TypicalDatedEvents.java
@@ -0,0 +1,18 @@
+package seedu.address.testutil;
+
+import java.util.ArrayList;
+
+import seedu.address.model.person.timetable.DatedEvent;
+
+/**
+ * A utility class containing a list of {@code DatedEvent} objects to be used in tests.
+ */
+public class TypicalDatedEvents {
+
+ public static final ArrayList NORMAL_DATEDEVENTS = new DatedEventsBuilder()
+ .withDatedEvent(DatedEvent.newDatedEvent("CS2103 Meeting 2023-10-10 1030 1130 y"))
+ .withDatedEvent(DatedEvent.newDatedEvent("Walk Dog 2023-10-10 1030 1130 n"))
+ .withDatedEvent(DatedEvent.newDatedEvent("Competitive sleeping 2023-10-10 1030 1130 y"))
+ .build();
+
+}
diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java
index fec76fb7129..037bc431447 100644
--- a/src/test/java/seedu/address/testutil/TypicalPersons.java
+++ b/src/test/java/seedu/address/testutil/TypicalPersons.java
@@ -2,6 +2,8 @@
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_BIRTHDAY_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_BIRTHDAY_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY;
@@ -10,6 +12,7 @@
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.testutil.TypicalSchedule.NORMAL_SCHEDULE;
import java.util.ArrayList;
import java.util.Arrays;
@@ -24,36 +27,41 @@
public class TypicalPersons {
public static final Person ALICE = new PersonBuilder().withName("Alice Pauline")
- .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com")
- .withPhone("94351253")
- .withTags("friends").build();
+ .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com").withPhone("94351253")
+ .withBirthday("2000-01-01").withTags("friends").build();
public static final Person BENSON = new PersonBuilder().withName("Benson Meier")
- .withAddress("311, Clementi Ave 2, #02-25")
- .withEmail("johnd@example.com").withPhone("98765432")
- .withTags("owesMoney", "friends").build();
+ .withAddress("311, Clementi Ave 2, #02-25").withEmail("johnd@example.com").withPhone("98765432")
+ .withBirthday("2000-01-01").withTags("owesMoney", "friends").build();
public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563")
- .withEmail("heinz@example.com").withAddress("wall street").build();
+ .withEmail("heinz@example.com").withBirthday("2000-01-01").withAddress("wall street").build();
public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533")
- .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends").build();
- public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224")
- .withEmail("werner@example.com").withAddress("michegan ave").build();
- public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427")
- .withEmail("lydia@example.com").withAddress("little tokyo").build();
- public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442")
- .withEmail("anna@example.com").withAddress("4th street").build();
+ .withEmail("cornelia@example.com").withAddress("10th street").withSchedule(NORMAL_SCHEDULE)
+ .withBirthday("2000-01-01").withTags("friends").build();
+ public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("94822243")
+ .withEmail("werner@example.com").withAddress("michegan ave").withSchedule(NORMAL_SCHEDULE)
+ .withBirthday("2000-01-01").build();
+ public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("94824272")
+ .withEmail("lydia@example.com").withAddress("little tokyo").withSchedule(NORMAL_SCHEDULE)
+ .withBirthday("2000-01-01").build();
+ public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("94824424")
+ .withEmail("anna@example.com").withAddress("4th street").withSchedule(NORMAL_SCHEDULE)
+ .withBirthday("2000-01-01").build();
// Manually added
- public static final Person HOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424")
- .withEmail("stefan@example.com").withAddress("little india").build();
- public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131")
- .withEmail("hans@example.com").withAddress("chicago ave").build();
+ public static final Person HOON = new PersonBuilder().withName("Hoon Meier").withPhone("84824242")
+ .withEmail("stefan@example.com").withAddress("little india").withSchedule(NORMAL_SCHEDULE)
+ .withBirthday("2000-01-01").build();
+ public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("84821313")
+ .withEmail("hans@example.com").withAddress("chicago ave").withSchedule(NORMAL_SCHEDULE)
+ .withBirthday("2000-01-01").build();
// Manually added - Person's details found in {@code CommandTestUtil}
public static final Person AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY)
- .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build();
+ .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withSchedule().withBirthday(VALID_BIRTHDAY_AMY)
+ .withTags(VALID_TAG_FRIEND).build();
public static final Person BOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB)
- .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND)
- .build();
+ .withEmail(VALID_EMAIL_BOB).withBirthday(VALID_BIRTHDAY_BOB).withAddress(VALID_ADDRESS_BOB)
+ .withSchedule(NORMAL_SCHEDULE).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER
@@ -70,6 +78,12 @@ public static AddressBook getTypicalAddressBook() {
return ab;
}
+ public static AddressBook getSmallTypicalAddressBook() {
+ AddressBook ab = new AddressBook();
+ ab.addPerson(GEORGE);
+ return ab;
+ }
+
public static List getTypicalPersons() {
return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE));
}
diff --git a/src/test/java/seedu/address/testutil/TypicalSchedule.java b/src/test/java/seedu/address/testutil/TypicalSchedule.java
new file mode 100644
index 00000000000..ed059b7b8a9
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/TypicalSchedule.java
@@ -0,0 +1,43 @@
+package seedu.address.testutil;
+
+import seedu.address.model.person.timetable.Cca;
+import seedu.address.model.person.timetable.DatedEvent;
+import seedu.address.model.person.timetable.Module;
+import seedu.address.model.person.timetable.Schedule;
+
+/**
+ * A utility class containing a list of {@code Schedule} objects to be used in tests.
+ */
+public class TypicalSchedule {
+
+ public static final Schedule NORMAL_SCHEDULE = new ScheduleBuilder()
+ .withModule(new Module("CS2103", "Wednesday 1200 1300"))
+ .withCca(new Cca("Basketball", "Monday 1800 2000"))
+ .withDatedEvent(DatedEvent.newDatedEvent("CS2103 Meeting 2023-10-10 1030 1130 y"))
+ .build();
+
+ public static final Schedule FULL_SCHEDULE = new ScheduleBuilder()
+ .withModule(new Module("CS2103", "Monday 0000 2400"))
+ .withModule(new Module("CS2101", "Tuesday 0000 2400"))
+ .withModule(new Module("CS2100", "Wednesday 0000 2400"))
+ .withModule(new Module("CS2103", "Thursday 0000 2400"))
+ .withModule(new Module("CS2101", "Friday 0000 2400"))
+ .withModule(new Module("CS2100", "Saturday 0000 2400"))
+ .withModule(new Module("CS2103", "Sunday 0000 2400"))
+ .build();
+
+ public static final Schedule FILLED_SCHEDULE = new ScheduleBuilder()
+ .withModule(new Module("CS2103", "Monday 0000 2400"))
+ .withModule(new Module("CS2101", "Tuesday 0000 2400"))
+ .withModule(new Module("CS2100", "Wednesday 0000 2400"))
+ .withModule(new Module("CS2103", "Thursday 0000 2400"))
+ .withModule(new Module("CS2101", "Friday 0000 2400"))
+ .withModule(new Module("CS2100", "Saturday 0000 2400"))
+ .withModule(new Module("CS2103", "Sunday 0000 2400"))
+ .build();
+
+ public static final Cca NORMAL_CCA = new Cca("Basketball", "Monday 1800 2000");
+ public static final Module NORMAL_MODULE = new Module("CS2103", "Wednesday 1200 1300");
+ public static final DatedEvent NORMAL_DATED_EVENT = DatedEvent
+ .newDatedEvent("CS2103 Meeting 2023-10-10 1030 1130 y");
+}
diff --git a/src/test/java/seedu/address/testutil/TypicalUsers.java b/src/test/java/seedu/address/testutil/TypicalUsers.java
new file mode 100644
index 00000000000..289649de5d9
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/TypicalUsers.java
@@ -0,0 +1,60 @@
+package seedu.address.testutil;
+
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_BIRTHDAY_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_BIRTHDAY_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.testutil.TypicalDatedEvents.NORMAL_DATEDEVENTS;
+import static seedu.address.testutil.TypicalSchedule.FILLED_SCHEDULE;
+import static seedu.address.testutil.TypicalSchedule.FULL_SCHEDULE;
+import static seedu.address.testutil.TypicalSchedule.NORMAL_SCHEDULE;
+
+import seedu.address.model.user.User;
+
+/**
+ * A utility class containing a list of {@code User} objects to be used in tests.
+ */
+public class TypicalUsers {
+
+ public static final User JOHN = new UserBuilder().withName("John Doe")
+ .withAddress("311, Clementi Ave 2, #02-25").withEmail("john@example.com")
+ .withPhone("98765432").withBirthday("1998-01-01").withSchedule().withDatedEvents()
+ .withTags("friends").build();
+
+ public static final User JANE = new UserBuilder().withName("Jane Doe")
+ .withAddress("321, Clementi Ave 4, #05-20").withEmail("jane@example.com")
+ .withPhone("92345678").withBirthday("2000-01-01").withSchedule(NORMAL_SCHEDULE)
+ .withTags("friends").build();
+
+ public static final User JAMES = new UserBuilder().withName("James Doe")
+ .withAddress("123, Clementi Ave 6, #05-22").withEmail("james@example.com")
+ .withPhone("92345679").withBirthday("2000-01-01").withSchedule(FULL_SCHEDULE)
+ .withTags("friends").build();
+
+ public static final User JOSH = new UserBuilder().withName("Josh Doe")
+ .withAddress("123, Clementi Ave 5, #05-22").withEmail("josh@example.com")
+ .withPhone("92345612").withBirthday("2000-01-01").withSchedule(FILLED_SCHEDULE).build();
+
+ // Manually added - User's details found in {@code CommandTestUtil}
+ public static final User AMY = new UserBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY)
+ .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withBirthday(VALID_BIRTHDAY_AMY)
+ .withSchedule().withTags(VALID_TAG_FRIEND).build();
+ public static final User BOB = new UserBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB)
+ .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withBirthday(VALID_BIRTHDAY_BOB)
+ .withSchedule(NORMAL_SCHEDULE).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND)
+ .withDatedEvents(NORMAL_DATEDEVENTS).build();
+
+ public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER
+
+ private TypicalUsers() {} // prevents instantiation
+
+}
+
diff --git a/src/test/java/seedu/address/testutil/UserBuilder.java b/src/test/java/seedu/address/testutil/UserBuilder.java
new file mode 100644
index 00000000000..800b12e2e7d
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/UserBuilder.java
@@ -0,0 +1,153 @@
+package seedu.address.testutil;
+
+import static seedu.address.testutil.TypicalDatedEvents.NORMAL_DATEDEVENTS;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+import seedu.address.model.person.Address;
+import seedu.address.model.person.Birthday;
+import seedu.address.model.person.Email;
+import seedu.address.model.person.Name;
+import seedu.address.model.person.Phone;
+import seedu.address.model.person.timetable.DatedEvent;
+import seedu.address.model.person.timetable.Schedule;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.user.User;
+import seedu.address.model.util.SampleDataUtil;
+
+/**
+ * A utility class to help with building User objects.
+ */
+public class UserBuilder {
+
+ public static final String DEFAULT_NAME = "Amy Bee";
+ public static final String DEFAULT_PHONE = "85355255";
+ public static final String DEFAULT_EMAIL = "amy@gmail.com";
+ public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111";
+ public static final String DEFAULT_BIRTHDAY = "2000-01-01";
+
+ private Name name;
+ private Phone phone;
+ private Email email;
+ private Address address;
+ private Birthday birthday;
+ private Set tags;
+ private Schedule schedule;
+ private ArrayList datedEvents;
+
+ /**
+ * Sets the {@code datedEvents} of the {@code User} that we are building.
+ */
+ public UserBuilder() {
+ name = new Name(DEFAULT_NAME);
+ phone = new Phone(DEFAULT_PHONE);
+ email = new Email(DEFAULT_EMAIL);
+ address = new Address(DEFAULT_ADDRESS);
+ birthday = new Birthday(DEFAULT_BIRTHDAY);
+ schedule = new Schedule();
+ tags = new HashSet<>();
+ datedEvents = NORMAL_DATEDEVENTS;
+ }
+
+ /**
+ * Initializes the UserBuilder with the data of {@code userToCopy}.
+ */
+ public UserBuilder(User userToCopy) {
+ name = userToCopy.getName();
+ phone = userToCopy.getPhone();
+ email = userToCopy.getEmail();
+ address = userToCopy.getAddress();
+ birthday = userToCopy.getBirthday();
+ schedule = userToCopy.getSchedule();
+ tags = new HashSet<>(userToCopy.getTags());
+ datedEvents = new ArrayList<>(userToCopy.getDatedEvents());
+ }
+
+ /**
+ * Sets the {@code Name} of the {@code User} that we are building.
+ */
+ public UserBuilder withName(String name) {
+ this.name = new Name(name);
+ return this;
+ }
+
+ /**
+ * Parses the {@code tags} into a {@code Set