UniMate is based off the AddressBook-Level3 project created by the SE-EDU initiative.
In this document, UniMateCalendar
and Calendar
are used synonymously.
Refer to the guide Setting up and getting started.
The Architecture Diagram given above explains the high-level design of the App.
Given below is a quick overview of main components and how they interact with each other.
Main components of the architecture
Main
(consisting of classes Main
and MainApp
) is in charge of the app launch and shut down.
The bulk of the app's work is done by the following four components:
UI
: The UI of the App.Logic
: The command executor.Model
: Holds the data of the App in memory.Storage
: Reads data from, and writes data to, the hard disk.Commons
represents a collection of classes used by multiple other components.
How the architecture components interact with each other
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command delete 1
.
Each of the four main components (also shown in the diagram above),
interface
with the same name as the Component.{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.
The sections below give more details of each component.
The API of this component is specified in Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, PersonListPanel
, StatusBarFooter
, BottomListPanel
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 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
is specified in MainWindow.fxml
The UI
component,
Logic
component.Model
data so that the UI can be updated with the modified data.Logic
component, because the UI
relies on the Logic
to execute commands.Model
component, as it displays Person
object residing in the Model
.API : 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.
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.
How the Logic
component works:
Logic
is called upon to execute a command, it is passed to an UniMateParser
object which in turn creates a parser that matches the command (e.g., DeleteCommandParser
) and uses it to parse the command.Command
object (more precisely, an object of one of its subclasses e.g., DeleteCommand
) which is executed by the LogicManager
.Model
when it is executed (e.g. to delete a person).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:
How the parsing works:
UniMateParser
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 UniMateParser
returns back as a Command
object.XYZCommandParser
classes (e.g., AddCommandParser
, DeleteCommandParser
, ...) inherit from the Parser
interface so that they can be treated similarly where possible e.g, during testing.API : Model.java
The Model
component,
Person
objects (which are contained in a UniquePersonList
object).Person
objects (e.g., results of a search query) as a separate filtered list which is exposed to outsiders as an unmodifiable ObservableList<Person>
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.UserPref
object that represents the user’s preferences. This is exposed to the outside as a ReadOnlyUserPref
objects.Model
represents data entities of the domain, they should make sense on their own without depending on other components)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.
API : Storage.java
The Storage
component,
AddressBookStorage
,CalendarStorage
, TaskManagerStorage
and UserPrefStorage
, which means it can be treated as either one (if only the functionality of only one is needed).Model
component (because the Storage
component's job is to save/retrieve objects that belong to the Model
)Classes used by multiple components are in the seedu.addressbook.commons
package.
This section describes some noteworthy details on how certain features are implemented.
//@@author lihongguang00
The addEvent feature is facilitated by the Calendar
class. It allows the users to block out some time
in their personal Calendar
with some Event
that has the following attributes:
DESCRIPTION
— Brief description of the Event
START_DATE_TIME
— Starting date and time of the Event
END_DATE_TIME
— End date and time of the Event
The syntax used to call this command is as follows: addEvent d/DESCRIPTION ts/START_DATE_TIME te/END_DATE_TIME
,
with the START_DATE_TIME
and END_DATE_TIME
in the yyyy-MM-dd HH:mm
format. If any of the fields are missing
or if the formatting is incorrect, an error message will be thrown along with usage instructions on the correct
formatting.
Given below is an example usage scenario and how the addEvent feature behaves at each step.
Step 1. The user launches the application for the first time. The user's personal Calendar
will be initialized
as an empty calendar.
Step 2. The user executes addEvent d/Go to school ts/2023-10-26 08:00 te/2023-10-26 16:00
to add the Event
of Go to school
from 2023-10-26 8am
to 2023-10-26 4pm
. This will call the UniMateParser#execute()
,
passing in the user input from the user.
Step 3. Since the command is an addEvent
command, it passes the user input to AddEventCommandParser#parse()
for parsing.
Step 4. The AddEventCommandParser#parse
command will parse the command into 3 argument fields —
DESCRIPTION
, START_DATE_TIME
and END_DATE_TIME
. The DESCRIPTION
is passed into
ParserUtil#parseEventDescription()
to produce a EventDescription
object, while the START_DATE_TIME
and END_DATE_TIME
are passed into ParserUtil#parseEventPeriod()
to produce a EventPeriod
object.
Note: If the DESCRIPTION
is empty, or the START_DATE_TIME
or END_DATE_TIME
are of invalid
format, a ParseException
will be thrown, displaying the appropriate command usage format.
Step 5. The EventDescription
and EventPeriod
objects produced in Step 4 are then passed into
the constructor for Event
, creating an Event
object with the respective user-defined attributes. This
Event
object is then passed into the constructor of the AddEventCommand
object.
Step 6. AddEventCommand#execute()
is then called, and the calendar will check if there is an existing
Event
that has a conflicting timing with the new Event
to be added. Since the calendar is empty,
no errors will be raised and the user will see his new Event
displayed in the UI in the My Calendar
region.
Note: Suppose there is a conflicting Event
that already exists in the Calendar
with the Event
to be added, the new Event
will not be added, and a message that states that there
is a timing conflict will be reflected in the UI status bar.
Aspect: Appropriate time period of an event:
Event
such as multi-day business trips.Event
, having to type the date twice when calling the command might be troublesome.Event
, user has to call the command multiple times for all the relevant days//@@author Fallman2
The deletion of events is facilitated by the model::deleteEventAt
method and the model::findEventAt
method. The
former method deletes the event stored in the Calendar
object which itself is an attribute of the Model
object by
calling a similar method Calendar::deleteEventAt
. These methods take in a LocalDateTime
object and finds the method
within the Calendar
object, then in the case of model::deleteEventAt
, deletes the event. Given below is an example
usage scenario of the command.
Step 1. The user launches the application and creates an event.
Step 2. The user executes deleteEvent 2023-12-09 12:00
command to delete the event at that time. The deleteEvent
command calls Model#findEventAt(LocalDateTime)
to find an event at the specified date and time. This event is then stored as a
variable toDelete
.
Note: If no event is found at the specified date and time at any point of the command execution, an
EventNotFoundException
is thrown which causes an error message to be displayed.
Step 3. The command then calls Model#deleteEventAt(LocalDateTime)
. This method calls similar methods of
Calendar#deleteEventAt(LocalDateTime)
which calls AllDaysEventListManager#deleteEventAt(LocalDateTime)
.
Step 4. The AllDaysEventListManager
checks for an event at the specified date and time again, then checks for all days
for which the event lasts for. Then, for each day, the event is removed from the SingleDayEventList
.
Step 5. The deleted event which was previously stored in as a variable is displayed in the CommandResult
to show the
user which command was deleted.
Design considerations
Aspect: Appropriate input:
All events within a time frame can be cleared using the ClearEvents
command.
The command requires the start and end time of the time frame to be cleared as inputs.
All events that fall within or intersect with the time frame (start time inclusive, end time exclusive) are selected to be deleted.
This command also has an optional confirmation as input. If the confirmation is not present in the input, the deletion will not be executed and instead, the events
that would have been executed are displayed in the result. The command can then be reentered with the confirmation to confirm the deletion.
Given below is an example usage scenario of the command.
Step 1. The user launches the application and creates multiple events.
Step 2. The user executes clearEvents ts/2023-02-03 12:00 te/2023-02-03 14:00
to view all events within the time range.
Step 3. The ClearEventsCommandParser
creates an EventPeriod
representing the indicated time frame and creates a new ClearEventsCommand
with this EventPeriod
.
Note: At this step, if no events are found within the EventPeriod
given, a CommandException
is thrown and an error message is displayed.
Step 4. The ClearEventsCommand
calls the Model#eventsInRange(EventPeriod)
method to find all events in the specified time range.
Step 5. All events within the time range are returned and displayed in the command result.
Step 6. The user views all events that would be deleted.
Step 7. The user executes clearEvents ts/2023-02-03 12:00 te/2023-02-03 14:00 c/CONFIRMED
to confirm the deletion.
Step 8. The ClearEventsCommand
calls the Model#eventsInRange(EventPeriod)
method to find all events in the specified time range.
Step 9. For each event in range, the command calls the Model#deleteEventsInRange(EventPeriod)
which deletes all events within the range.
Step 10. All deleted events are displayed in the command result.
When the command compareCalendars
is called, the arguments passed by the users are converted into a list of Index
in CompareCalendarByIndexCommandParser#parse()
.
The list of Index
is then passed as an argument into the constructor CompareCalendarByIndexCommand#new()
. Subsequently, when CompareCalendarByIndexCommand#execute()
is called,
the list of Person
stored in the AddressBook is retrieved using ModelManger#getFilteredPersonList()
, and the Person
associated with each Index
is extracted
with List#get()
. Their respective UniMateCalendar
is then extracted with Person#getCalendar()
, and all the UniMateCalendar
of the user and relevant contacts are
merged and reduce into a single UniMateCalendar
with UniMateCalendar#combineCalendar()
. This combined UniMateCalendar
is then used to produce the grey event cards
seen in the pop-up comparison calendar window displayed to the user with CalendarEventSpace#addSolidEventCards()
.
Step 1. The user launches the application and creates an event in the current week.
Step 2. The user creates an event in the current week for one of their contact. For this example, let's assume the contact is Alex Yeoh
at Index
1 of the AddressBook.
Step 3. The user executed compareCalendars 1
. The CompareCalendarByIndexCommandParser#parse()
creates a list of Index
containing a single Index
object from
created from the user's argument 1
, and passed it into CompareCalendarByIndexCommand#new()
.
Step 4. The CompareCalendarByIndexCommand#execute()
will turn the Index
list into a Stream
with Arrays#stream()
, and each Index
in the stream will be
converted into the Person
with the given Index
with Stream#map()
and List#get()
as the map()
function input.
Step 5. The stream is further converted into a Stream
of UniMateCalendar
with Stream#map()
and Person#getCalendar()
as the map()
function input.
Step 6. Eventually, we call Stream#reduce()
with the user's UniMateCalendar
as seed, merging all of the UniMateCalendar
in the Stream
into a single one with UniMateCalendar#combineCalendar()
.
Step 7. The resultant UniMateCalendar
then has the Event
stored in it converted into grey event cards with CalendarEventSpace#addSolidEventCards()
, which
reflects in the resultant pop-up comparison calendar window.
Design Consideration
Aspect: Whether to show calendar on a new pop-up or in the main GUI:
The filtering function executed by FilterCommand
is facilitated by the PersonFilter
class.
which itself is similar to the EditPersonDescriptor
class found in EditCommand.java
. It stores the fields by which
the contacts are to be filtered and creates a predicate to facilitate the filtering. Notably, it implements the
following operation:
PersonFilter#matchesFilter(Person)
- Compares the values of the attributes of the Person
to the strings stored as
attributes in the PersonFilter
object. This method is later used as a lambda method to filter the contact list.Given below is an example of how the filter function works at each step.
Step 1. The user executes filter n/Bob t/CS
to filter contacts to see only people with "Bob" in their name and have at
least 1 tag with "CS" in it. The input is passed to UniMateParser
which then parses it with the FilterCommandParser
.
Step 2. The FilterCommandParser
parses the input and creates a corresponding PersonFilter
object with null for all
parameters. It then sets all specified attributes of the created PersonFilter
while leaving unspecified fields as
null. The FilterCommandParser
finally returns a newly created FilterCommand
with the PersonFilter used in the
constructor.
Step 3. FilterCommand#execute
is called. In this method, model#updateFilteredPersonList
is called with
PersonFilter#matchesFilter
being used as the predicate. This updates the GUI and populates the filtered list with
only Person
objects that match the filter.
Step 4. The number of people displayed is returned as a CommandResult
.
//@@author nicrandomlee
The sort function executed by SortCommand
.
The sort function allows users to sort all persons in UniMate based on a given criteria, and has the following attributes:
COMPARATOR
— AddressBook sorting criteriareverse
— Determines if sorting is by descending orderThe syntax used to call this command is as follows: sort /COMPARATOR [/reverse]
,
with the COMPARATOR
being one of /byname
, /byemail
, /byphone
, /byaddress
to sort by name, email, phone and address respectively. If any of the fields are missing or if the formatting is incorrect, an error message will be thrown along with usage instructions on the correct formatting. The /reverse
parameter is optional to sort in descending order instead.
Given below is an example of how the sort function works at each step. We will simulate a user using the sort function to sort UniMate contacts by name in descending order.
Step 1. The user executes sort /byname /reverse
to find his friend's contact. The input is passed into UniMateParser
which then parses it with the SortCommandParser
.
Step 2. The SortCommandParser
parses the input and first checks for arguments provided. If the arguments are empty, invalid or in the wrong format, a helper message will appear to allow the user to reference the sample run case. The arguments are then matched by the keywords provided to determine the basis for sorting using a SortComparator
. All the comparators are added into an ArrayList of SortComparator
for SortCommand
to parse.
Step 3. SortCommand
is initialized parses the array from step 2 to determine the basis of comparison when the command is executed. The SortCommandParser
finally returns a newly created SortCommand
consisting of a Person Comparator that decides the method of sorting for the UniMate address book.
Step 4. SortCommand#execute
is called. In this method, model#sortPersonList
is called with the Person Comparator created in step 3. This in turn calls AddressBook#sortPersons
which calls the storage function to save the contacts in the json file based on the sorted order.
Step 5. The GUI then reads in the json file to obtain the order of addresses and populates the sorted list with the sorting criteria provided.
Step 6. The success message is returned as a CommandResult
and displayed on the GUI result display panel.
Here's a sequence diagram to summarise the steps above:
Design considerations
sort
command is dependent on the structure of the AddressBookStorage
object. Should the structure
of how the AddressBook objects are stored change, a new implementation will be required for the command.//@@author
The task list feature is facilitated by 'TaskManager'. It extends a ReadOnlyTaskManager that will be used for
saving users' tasks. The data of the TaskManager is contained in a TaskList
object. Additionally, it implements the following operations:
TaskManager#addTask(Task)
-- Adds a task to the current task list and saves it to memory.TaskManager#deleteTask(int)
-- Delete an existing task from the current task list as indicated by its index and saves the change to memory.TaskManager#sortTasksBy(String)
-- Sets the comparator by which the internal TaskList is sorted to one of two preset options.
This method only accepts the strings "Description"
or "Deadline"
as input and throws an error otherwise.TaskManager#getTaskList()
-- Returns and exposes the internal TaskList
as an unmodifiable ObservableList<Task>
that can be 'observed'.These operations are exposed in the Model
interface as Model#addTask(Task)
, Model#deleteTask(int)
, Model#sortTasksBy
and Model#getTaskList
respectively.
A Task
object consists of a TaskDescription
and can have up to 1 Deadline
.
The adding of tasks is facilitated by the TaskManager#addTask(Task)
method.
The method adds a Task
to the TaskList
object which itself is an attribute of the TaskManager
object by
calling a similar method TaskList#addTask(Task)
.
These methods take in a Task
object and adds the Task
object to the TaskList
.
Given below is an example usage scenario of the command.
Step 1. The user launches the application.
Step 2. The user executes addTask d/CS2105 Assignment te/2023-12-12 12:00
command to add the task.
Note: While Tasks
can have the same TaskDescription
or Deadline
, they cannot have both the same TaskDescription
and Deadline
.
Step 3. The addTask
method in the model is called, which calls the addTask
method in the TaskManager, adding the task to the TaskList
.
Step 4. The TaskList
is saved to the memory.
Design considerations
addTask
command is such that a deadline is made optional since some tasks are recurring or do not have a specific deadline.Tasks can only be viewed in the bottom panel of the GUI which, by default, shows the events list instead.
The viewing of tasks is facilitated by the SwitchListCommand
which interacts with the UI to swap the bottom list between the event list and the task list.
Given below is an example usage scenario of the command.
Step 1: The user launches the application.
Step 2: The user executes switchList
command to view the task list.
Note: If the task list is already displayed, the command switches the list back to the event list instead.
Step 3: The MainWindow
class of the UI switches the bottom list to the task list. The task list will now display the task list, allowing the user to view all tasks.
Design considerations
Aspect: Mode of display:
The deletion of tasks is facilitated by the TaskManager#deleteTask(int)
method.
The method deletes a Task
from the TaskList
object which is an attribute of the TaskManager
object by calling a similar method TaskList#deleteTask(int)
.
This in turn deletes the task in TaskList
that is indicated by its index within the TaskList
.
Given below is an example usage scenario of the command.
Step 1. The user launches the application.
Step 2. The user executes switchList
command to view the task list on the GUI.
Step 3. The user executes deleteTask 1
command to delete the first task shown in the task list as displayed in the GUI.
Step 4. The deleteTask
method in the model is called, which calls the deleteTask
method in the TaskManager, deleting the task from the TaskList
.
Step 5. The TaskList
is saved to the memory.
Design considerations
Aspect: Appropriate Input:
Tasks in the TaskList can be sorted either alphabetically by description or by deadlines. The accepted inputs for these are sortTasks Description
and sortTasks Deadline
respectively.
The sorting of tasks is facilitated by the sortTasks
command.
Given below is an example usage scenario of the command.
Step 1. The user launches the application.
Step 2. The user switches to the task list with the switchList
command.
Step 3. The user executes the sortTasks Description
command to sort tasks by description alphabetically.
Step 4. A SortTasksCommand
is created after the command is parsed by the SortTasksCommandParser
.
Step 5. Upon execution, the SortTasksCommand
calls Model#sortTasks(String)
and passes the sorting method of Description
represented by a String to it.
Step 6. The TaskManager#sortTasks(String)
method is called and the sortingOrder
attribute of type Comparator<Task>
of the TaskManager
is set to the appropriate type.
Step 7. The internal TaskList
is sorted by the sortingOrder
.
//@@author nicrandomlee
The Edit Contact Event function executed by editContactEvent
allows users to edit all person's calendar events in UniMate, and has the following attributes:
PERSON_INDEX
— Index of the target personEVENT_INDEX
— Index of the target person's calendar eventDESCRIPTION
— Brief description of the edited Event
NEW_START_DATE_TIME
— Starting date and time of the edited Event
NEW_END_DATE_TIME
— End date and time of the edited Event
The syntax used to call this command is as follows: editContactEvent PERSON_INDEX EVENT_INDEX [d/DESCRIPTION] [ts/NEW_START_DATE_TIME][te/NEW_END_DATE_TIME]
, with the START_DATE_TIME
and END_DATE_TIME
in the yyyy-MM-dd HH:mm
format. If any of the fields are missing or if the formatting is incorrect, an error message will be thrown along with usage instructions on the correct formatting.
Given below is an example of how the editContactEvent function works at each step. We will simulate a user using the editContactEvent function to sort UniMate contacts by name in descending order.
Step 1. The user executes editContactEvent 1 1 d/CS2103 meeting ts/2023-11-11 10:00 te/2023-11-11 12:00
to reschedule the meeting with his CS2103 module group mate. The input is passed into UniMateParser
which then parses it with the EditContactEventCommandParser
.
Step 2. The EditContactEventCommandParser
parses the input and first checks for arguments provided. If the arguments are empty, invalid or in the wrong format, a helper message will appear to allow the user to reference the sample run case. The arguments are then matched by delimiters d/
, ts/
amd te/
to determine the fields to be edited. If certain fields are empty (for example, the user just wants to change the time start and time end of the meeting), the event description from the edited event will be retained when EditContactEventCommand#execute
is executed in step 4.
Step 3. EditContactEventCommandParser
creates a temporary event EditEventDescriptor
as well as an array consisting of two elements, that is the PERSON_INDEX and EVENT_INDEX to be parsed. The EditContactEventCommandParser
finally returns a newly created EditContactEventCommand
consisting of the array and the temporary event.
Step 4. EditContactEventCommand#execute
is called. In this method, a new person object is created with the same attributes except an updated calendar with the updated event instead. model#setPerson
is called to replace the target person with the new person. The observable list is then refreshed to show the new calendar event.
Step 5. The success message is returned as a CommandResult
and displayed on the GUI result display panel.
Here's a sequence diagram to summarise the steps above:
Design considerations
EditContactEvent
command is dependent on the structure of the CalendarStorage
object. Should the structure of how the Calendar objects are stored change, a new implementation will be required for the command.
//@@authorThe 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:
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.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.
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.
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.
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
.
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.
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 following sequence diagram shows how the undo operation works:
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.
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.
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.
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.
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 following activity diagram summarizes what happens when a user executes a new command:
Aspect: How undo & redo executes:
Alternative 1 (current choice): Saves the entire address book.
Alternative 2: Individual command knows how to undo/redo by itself.
delete
, just save the person being deleted).{more aspects and alternatives to be added}
{Explain here how the data archiving feature will be implemented}
Target user profile:
Value proposition: manage contacts faster than a typical mouse/GUI driven app
//@@author lihongguang00
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
* * * | NUS student | search for the contacts of other students within my university | contact them for group projects |
* * * | NUS student | search for a name in the contact | easily find the person’s contact |
* * * | NUS student | add contacts into my address book easily | retrieve the saved contact |
* * * | NUS student living on campus | save the contacts of my neighbors | contact them in case of any emergencies |
* * * | NUS student living on campus | save the addresses of my neighbours | locate them easily when necessary |
* * * | NUS student in multiple CCAs | filter my contacts by tags to identify all people in a group | find the relevant contacts in a certain group quickly |
* * * | student staying on campus | label multiple tags to my contacts | locate my friends taking the same module and staying in the same campus residence as me |
* * * | user that is familiar with the keyboard | use the keyboard to type commands in the applications | access the features of the application |
* * * | user with bad memory | save a short description of my contact | identify my contacts better |
* * * | visual-reliant user | save a photo of the person into my contacts | quickly recognise and find them |
* * * | non-tech-savvy user | use the help feature of the app | navigate about the app easily |
* * | NUS student | sort all contacts by name | easily find a person's contact |
* * | NUS student | sort all contacts by name in reverse | easily find a person's contact |
* * | NUS student | sort all contacts by email | easily find a person's contact |
* * | NUS student | sort all contacts by email in reverse | easily find a person's contact |
* * | NUS student | sort all contacts by address | easily find a person's contact |
* * | NUS student | sort all contacts by address in reverse | easily find a person's contact |
* * | NUS student | sort all contacts by phone | easily find a person's contact |
* * | NUS student | sort all contacts by phone in reverse | easily find a person's contact |
* * | NUS student | import the NUS calendar into the application | view all academic commitments more conveniently |
* * | NUS Student | compare timetables/calendars with my peers easily | plan meetings more conveniently |
* * | NUS student | allocate tasks and responsibilities within a project or CCA group | tasks can be done efficiently |
* * | NUS Student in multiple CCAs | group my contacts | identify which group my contacts belong to |
* * | NUS student doing a group project | export my calendar in my application | send it to my teammates to coordinate meeting times |
* * | forgetful user | view a password hint | I can recall my password when I forget it |
* * | forgetful student | be reminded of upcoming assignments | prioritize which assignment to work on first |
* * | forgetful student | be reminded of upcoming examinations | prioritize which exam module to study on first |
* * | forgetful student | be reminded of upcoming projects | prioritize which project to work on first |
* * | forgetful student | be reminded of upcoming tutorials | so that I can prioritize which tutorial to complete first |
* * | security conscious user | password-protect my appliation | prevent others from accessing my application profile easily |
* * | data conscious user | backup my data | so that I can recover it in the case my data is corrupted |
* * | user who cares about user experience | change the color of my user interface | modify the interface to my liking |
* * | user with a strong urge for aesthetics | interact with a clean user interface | feel at ease when using the application |
* * | non-tech-savvy user that does not know how to use terminal commands | use buttons around the application to navigate around the application easily | |
* | student who prefers using a tablet | I can use the application on my tablet | access contacts easily on my tablet |
* | student who prefers mobile devices | I can use the application on my mobile phone | sync my contacts with those in the application |
* | student in a group project | send a QR code of my application contact details | have my teammates add my contact details on the application much faster |
* | user who prefers cloud storage | sync the contacts in my application with Google Contacts | save it in cloud-based storage |
* | user accustomed to a PC | view all commands at once | explore features quickly |
* | user with a tendency to open many applications at once | have the application time out and exit | ensure my computer would not be cluttered by too many applications |
{More to be added} //@@author nicrandomlee
(For all use cases below, the System is the UniMate
and the Actor is the user
, unless specified otherwise)
Use case: UC1 List persons
MSS
Use case ends.
Extensions
2a. The list is empty.
2b. UniMate displays a message to the user.
Use case ends.
Use case: UC2 Delete a person
MSS
Use case ends.
3a. The given index is invalid.
3a1. UniMate shows an error message.
Use case resumes at step 2.
Use case: UC3 Add a person
MSS
Use case ends.
Extensions
1a. The given format is wrong
Use case ends.
1b. Person is already in UniMate
Use case ends.
//@@author Fallman2
Use case: UC4 Clear All Entries
MSS
User requests to clear all entries in UniMate
UniMate deletes all entries
Use Case Ends.
Extensions
1a. UniMate has no data
Use Case Ends
Use case: UC5 Edit persons
MSS
Extensions
Use case: UC6 Exit application
MSS
Use case: UC7 Search by name
MSS
//@@author junhonglow
Use case: UC8 View events
MSS
Use Case ends.
Extensions
2a1. UniMate displays a message indicating that there are no events.
Use case ends.
Use case: UC9 Add an event
MSS
Use case ends.
Extensions
2a. UniMate detects a conflict with an existing event.
Use case ends.
Use case: UC10 Delete an event
MSS
Use case ends.
Extensions
2a1. UniMate shows an error message.
Use case ends.
Use case: UC11 Edit an event
MSS
Extensions
2a. Event does not exist.
2a1. UniMate shows an error message.
Use case ends.
3a. UniMate detects a conflict with an existing event.
Use case ends.
//@@author Fallman2
Use case: UC12 Filter by fields.
MSS
User requests to list persons with specific words in specific fields.
UniMate displays persons matching all provided keywords in specified fields and the number of people matching all specified fields.
Use case ends.
Extensions
UniMate displays a message that 0 people have been displayed.
Use case ends.
UniMate displays all available contacts.
Use case ends.
//@@author nicrandomlee
Use case: UC13 Sort by fields.
MSS
User requests to sort persons
UniMate displays persons in specified order and by specified parameter
Use case ends.
Extensions
//@@author
//@@author lihongguang00
Use case: UC14 Compare calendars
MSS
Use case ends.
Extensions
2a. User inputs and confirms the tags of the contacts to identify who they want to compare their calendars with.
Use case resumes from step 3
2b. User inputs the invalid arguments for the command.
//@@author
//@@author Fallman2
Use case: UC15 Adding a Task.
MSS
Use case ends.
Extensions
1a. User leaves the description field empty.
Use case ends.
1b. User attempts to create a Task that is identical to another Task in the Task Manager.
Use case ends.
Use case: UC16 Viewing the task list.
MSS
Use case ends.
Extensions
1a. The list displayed is already the task list.
Use case ends.
Use case: UC17 Deleting a task.
MSS
Use case ends.
Extensions
2a. There are no tasks to delete .
Use case ends.
Use case: UC18 Sorting Tasks.
MSS
Use case ends.
Extensions
2a. User requests to sort the tasks by deadline instead.
Use case ends.
Use case: UC19 Clearing Events within a Time Range.
MSS
Use case ends.
Extensions
1a. Time range provided has a start time after the end time.
Use case ends.
1b. There are no events within the time range.
Use case ends.
//@@author
Project Constraints:
11
or above installed.Process Requirements:
Given below are instructions to test the app manually.
Note: These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.
Initial launch
Download the jar file and copy into an empty folder
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
Resize the window to an optimum size. Move the window to a different location. Close the window.
Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
Shutting down via command
Launch the app.
Type exit
into the input box and press ENTER
.
Shutting down via file
Launch the app.
Click on file
in the top menu bar.
Click on exit
in the dropdown menu.
Deleting a person while all persons are being shown
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.
Prerequisites: List all persons using the list
command. Multiple persons in the list.
1Test case: filter n/ p/ e/
Expected: All persons displayed.
Test case: filter n/o
Expected: All persons with an "o" in their name displayed.
Test case: filter n/o p/8 e/@ t/CS t/10
Expected: All persons with an "o" in their name, an "8" in their phone number, a tag that contains "CS" and a tag
that contains "10" displayed.
Test case: filter
Expected: Error message displayed that states that the command format is invalid. all persons shown.
Prerequisites: List all persons using the list
command. Multiple persons in the list.
Test case: sort /byname
Expected: All persons sorted alphabetically by name.
Test case: sort /byname /reverse
Expected: All persons sorted in reverse alphabetical order by name.
Test case: sort /byemail
Expected: All persons sorted by email.
Test case: sort /byphone
Expected: All persons sorted by phone number.
Test case: sort /byaddress
Expected: All persons sorted by address.
Test case: sort
Expected: Error message displayed indicating invalid command format.
Prerequisite: Empty event list.
addEvent d/Cry about deadlines ts/2023-01-01 00:01 te/2023-12-31 23:59
addEvent d/Laugh about deadlines ts/2023-01-02 00:01 te/2023-01-01 23:59
addEvent d/Cry about deadlines ts/2023-01-01 00:01 te/2023-12-31 23:59
addEvent d/Cry about deadlines ts/2023-01-01 00:01 te/2023-12-31 23:59
Prerequisite: Empty event list.
addEvent d/Cry about deadlines ts/2023-01-01 00:01 te/2023-12-31 23:59
deleteEvent 2023-02-02 00:01
deleteEvent 2023-02-02 00:01
Prerequisite: Empty event list.
addEvent d/Cry about deadlines ts/2023-01-01 00:01 te/2023-01-02 23:59
addEvent d/Cry about deadlines ts/2023-01-03 00:01 te/2023-01-04 23:59
clearEvents ts/2023-01-01 00:02 te/2023-01-04 23:00
clearEvents ts/2023-01-01 00:02 te/2023-01-04 23:00
addEvent d/Cry about deadlines ts/2023-01-01 00:01 te/2023-01-02 23:59
clearEvents ts/2023-01-01 00:02 te/2023-01-04 23:00
Prerequisite: Event list currently being displayed.
switchList
switchList 123
Prerequisite: 1 person in AddressBook, person currently has no events.
addContactEvent 1 d/Cry about deadlines ts/2023-01-01 00:01 te/2023-12-31 23:59
addContactEvent 1 d/Laugh about deadlines ts/2023-01-02 00:01 te/2023-01-01 23:59
addContactEvent 1 d/Cry about deadlines ts/2023-01-01 00:01 te/2023-12-31 23:59
addContactEvent 1 d/Cry about deadlines ts/2023-01-01 00:01 te/2023-12-31 23:59
Prerequisite: 1 person in AddressBook, person currently has no events.
addContactEvent 1 d/Cry about deadlines ts/2023-01-01 00:01 te/2023-12-31 23:59
deleteContactEvent 1 2023-02-02 00:01
deleteContactEvent 1 2023-02-02 00:01
Prerequisite: 1 person in AddressBook, person currently has no events.
addContactEvent 1 d/Cry about deadlines ts/2023-01-01 00:01 te/2023-12-31 23:59
editContactEvent 1 1 d/Edited Description
addContactEvent 1 d/Cry about deadlines ts/2023-01-01 00:01 te/2023-12-31 23:59
editContactEvent 1 1 ts/2023-01-01 00:20
addContactEvent 1 d/Cry about deadlines ts/2023-01-01 00:01 te/2023-12-31 23:59
editContactEvent 1 1 ts/2023-01-03 00:20
addContactEvent 1 d/Cry about deadlines ts/2023-01-01 00:01 te/2023-12-31 23:59
editContactEvent 1 1 te/2023-01-04 00:20
addContactEvent 1 d/Cry about deadlines ts/2023-01-02 00:01 te/2023-12-31 23:59
editContactEvent 1 1 t3/2023-01-01 00:20
addContactEvent
Prerequisite: 1 person in AddressBook, person currently has no events.
viewContactEvents 1
addContactEvent 1 d/Cry about deadlines ts/2023-01-01 00:01 te/2023-12-31 23:59
viewContactEvents 1
viewContactEvents 2
viewContactEvents
Prerequisite: 3 persons in AddressBook.
compareCalendars
compareCalendars 1 3
compareCalendars 4
Prerequisite: 3 persons in AddressBook, 2 persons have the tag "CS2103".
compareGroupCalendars
compareGroupCalendars CS2103
Prerequisite: No tasks in the task list.
addTask d/Hydrate te/2023-11-13 22:30
addTask d/Hydrate
addTask d/Hydrate te/2023-11-13 22:30
addTask d/Hydrate te/2023-11-13 22:30
Prerequisite: No tasks in the task list.
addTask d/Hydrate te/2023-11-13 22:30
deleteTask 1
deleteTask 1
Prerequisite: Multiple tasks in the task list.
sortTasks DESCRIPTION
Expected: Tasks are sorted alphabetically by description.sortTasks DEADLINE
Expected: Tasks are sorted by deadline.sortTasks 1askdj
Expected: Error message displayed indicating that the sorting order indicated is invalid.Prerequisites: Have all three json data files present.
Some sample storage json files for the tests can be found in the link here.
Rename the files to their respective data files and replace the original files (if any) found in the original directory.
Test case: Navigate to the addressbook.json file and delete the file.
Expected: The AddressBook (Contact List) in UniMate should be populated with sample data.
Test case: Single or multiple deletion of the different data files.
Expected: Similar to previous.
Test case: Navigate to the addressbook.json file and edit any of the fields to become an invalid field.
Example: Modify the events field of a contact to the following.
{
"persons" : [ {
"name" : "Alex Yeoh",
"phone" : "87438807",
"email" : "alexyeoh@example.com",
"address" : "Blk 30 Geylang Street 29, #06-40",
"tags" : [ "friends" ],
"events" : [ {
"description" : "Nap",
"eventPeriod" : "Invalid Period - 2024-01-03 18:00"
} ]
}
Expected: The AddressBook (Contact List) in UniMate should be blank as UniMate will discard all data.
Note: In this scenario, the event period is invalid.
Test case: Single or multiple corruption of the different data files.
Expected: Similar to previous.
Test case: Navigate to the addressbook.json file and duplicate a person in the file.
Example:
{
"persons" : [ {
"name" : "Bernice Yu",
"phone" : "99272758",
"email" : "berniceyu@example.com",
"address" : "Blk 30 Lorong 3 Serangoon Gardens, #07-18",
"tags" : [ "colleagues", "friends" ],
"events" : [ ]
}, {
"name" : "Bernice Yu",
"phone" : "99272758",
"email" : "berniceyu@example.com",
"address" : "Blk 30 Lorong 3 Serangoon Gardens, #07-18",
"tags" : [ "colleagues", "friends" ],
"events" : [ ]
} ]
}
Expected: The AddressBook (Contact List) in UniMate should be blank as UniMate will discard all data.
Test case: Duplication of the fields in the different data files.
Expected: Similar to previous.
Test case: Other invalid storage formats: Not a JSON format.
Expected: Similar to previous.
This part was especially difficult because we wanted to conform to the structure of the base AB3
code, knowing it is a brownfield project.
Thus, we wanted to ensure the abstraction granularity of the classes matched that of AB3
(e.g. Separate classes for every single attribute of Person
),
which explains why the UniMateCalendar
class is so granular, as seen in the model class diagram.
Furthermore, we knew from the beginning that we wanted to allow users to compare their Calendar
with that of their contacts.
Therefore, from the start, we had to write code that would make this feature possible.
The UI for the calendar was also challenging because nobody in the group had experience in desktop application UI development and JavaFx. Understanding JavaFx, before even implementing it in the creation of new GUI elements, required a few hours and much trial and error. It was also challenging to implement GUI components while ensuring that all elements that were already previously implemented worked as well.
Lastly, the feature where the calendar would "grow" and "shrink" according to the events that the user have in real time
took a lot of careful problem-solving to avoid breaking abstraction barrier and introducing cyclic dependencies, since
we had to establish a line of communication between the UniMateCalendar
of the Model
interface and the UI
interface,
and the Observable
and Observer
interfaces were only introduced later into the module, by which we had already implemented
this feature.
Integrating the calendar into the contacts in the AddressBook was challenging, but was slightly easier as we could just reuse the
code that we have written for the UniMateCalendar
. However, we still had to learn how to make the calendar of the contacts show up
in a new window rather than in the main application GUI.
For the Task Manager, the main difficulty lies in following the structure of the AB3
code.
While the implementation of the TaskManager was not complicated, we wanted to properly abstract the different classes from each other similarly to the
structure of AB3. Thus, the implementation required a large number of lines of code and methods, all of which required even more JUnit tests, multiplying
the number of hours spent on implementing the TaskManager. However, the implementation of the GUI for the TaskManager did not take as long as that
of the Calendar as the TaskManager used the same area of the GUI as the event list. That being said, given that no one in our team had experience working
on a GUI, especially with JavaFx, a few hours had to be spent in researching what would be the best way to carry out the switching of the lists from one to the other.
Additionally, for both Task List
and Event List
, making the list update in real time when new Task
/Event
are added was also difficult. When implementing
the GUI, we had wanted to ensure that commands executed in the main window would reflect appropriately in both the user's own task and event lists but also
for the pop-up event list of individual persons in the AddressBook. This proved to be another challenge as we would encounter several bugs while implementing
this feature. However, we managed to work together as a group to fix most of them.
Task manager
and Calendar
, successfully into the base AB3
codeTask List
and Event List
Currently, for the ClearEventsCommand, the user has to re-enter the command entirely with the confirmation text in order to confirm the deletion of the events. This implementation has an advantage of being able to bypass the confirmation check if the user wishes to delete the events more quickly but is otherwise clumsier to use. One way we tried to circumvent this is by displaying the message with the confirmation in the result shown but this would not be as convenient for users who do not wish to use their mouse. A better implementation would be to have the confirmation only require the user to key in Y or N to confirm or deny the deletion, allowing the events to still be deleted quickly for users who wish to do so but also allowing for less text to be keyed in to confirm.
Currently, there are only two ways to sort tasks. This can cause some tasks to be buried under others if they are not prioritized in either sorting method. Being able to sort tasks by proximity to the current date or being able to reverse the order of sorting of the tasks may help in making all tasks visible to the user so as to ensure that all tasks are completed on time.
Currently, the calendar for the user and contacts is restricted to viewing the current week's events. Therefore, the
user has no means of navigating looking at their events for other weeks other than the current one. This might impede
the user experience as the user might want to check their timetable for some timeframe in the near future (e.g. proceeding week).
Therefore, allowing the user to view their events for any week beyond just the current one might improve user experience,
reducing the dependency the user has on the Event List
to view events beyond this chronological restriction.
Currently, the user can only compare calendars with their contacts for the current week. Therefore, if the user wants to schedule a meeting in a week beyond the current one, they might have difficulty as they might not be able to compare their calendars with their contacts for that specific week. Therefore, allowing the user to navigate beyond the current week restriction for the compare calendar feature will be hugely beneficial to the user.
Currently, when the user wants to add an Event
into their Calendar
, they have to type the date of the event twice
(once in start date and time and another in end date and time). For single day events, this can be inconvenient. Hence,
we hope to introduce a command that will allow users to add a single day event without typing the date twice.
Currently, our Calendar
only supports adding of singular events. We hope to allow users to add recurring events in the future
to more accurately simulate the events that the user might have in a University context, where they might have recurring classes
every week.
Currently, UniMate has no support for importing of external files. Allowing users to import .ics files, which is a common export format of other calendar platforms, will allow UniMate to integrate more seamlessly with other calendar applications.