Project: iContacts
iContacts is an application that targets university students, aiming to help them manage their contacts and other aspects of their university life in an efficient manner.
iContacts is a Command Line Interface (CLI) application, where most of the commands are executed by typing rather than clicking on buttons.
Through iContacts, users would be able to store, manage and search through contact information efficiently and effectively.
Not only that, users would be able to set display pictures for each contact and also add reminders for upcoming events.
Last but not least, iContacts allow users to easily share contact information amongst each other via its import and export mechanisms.
This portfolio is to collate my contributions towards the developing of iContacts. It also showcases the software programming knowledge I have learned during the course of CS2103T, as well as how I utilized this knowledge and applied it to a real-world project. This portfolio also contains some suggested enhancements to be developed for iContacts in the future.
Code contributed: [Functional code] [Test code]
Adding a reminder: addreminder
Adds a reminder to iContacts.
Format: addreminder rd/REMINDER d/DATE ti/TIME
Examples:
-
addreminder rd/Dinner with family d/10/10/2017 ti/18:00
(Refer to Figure 2) -
addreminder rd/CS2105 Assignment d/26.10-2017 ti/23:59
Figure 2: Reminder for a dinner with family on 10/10/2017 at 18:00.
The reminder cells are colored differently according to the urgency of the event:
Figure 3: A reminder of an event happening today.
Figure 4: A reminder of an event happening within three days.
Figure 5: A reminder of an event happening more than three days later.
|
Editing a reminder: editreminder
Edits an existing reminder in iContacts.
Format: editreminder INDEX [rd/REMINDER] [d/DATE] [ti/TIME]
Examples:
-
editreminder 1 rd/Drink coffee
Edits the content of the 1st reminder to beDrink coffee
. -
editreminder 3 d/25/12/2017 ti/19:00
Edits the date and time of the 3rd reminder to be25/12/2017
and19:00
respectively.
Deleting a reminder: deletereminder
Deletes a specified reminder from iContacts.
Format: deletereminder INDEX
Examples:
-
deletereminder 1
Deletes the 1st reminder in iContacts. -
deletereminder 20
Deletes the 20th reminder in iContacts.
End of Extract
Justification
While iContacts allow users to store, manage and search for contact information efficiently, this alone is not sufficient to meet the needs of university students. Students often have multiple outstanding tasks or events such as assignments or meetings. Hence, what these students need is the ability to set reminders for upcoming tasks or events.
We chose to display the reminders chronologically instead of having them pop-up when the event is nearing as we feel that it is more user-friendly to permanently display the reminders for the users to see. Displaying the reminders permanently can grant users a sense of the quantity of upcoming tasks and events. Also, only reminding the users of an upcoming task or event when it is nearing might give the user insufficient time to prepare for it. Instead, permanently displaying the reminders with a countdown to the event allow users to better prepare for it.
We also recognize that there is a need to differentiate reminders based on the level of urgency. Therefore, we chose to do this by coloring the reminder cells differently. Using color is simple but powerful.
Reminder Implementation
Figure 36: UML class diagram showing the make-up of Reminder objects.
The Reminder
object represents a reminder in iContacts. It contains a String
variable reminder
, which represents
the actual reminder from the user, and also a Date
, Time
and Status
object, which represents the date, time and status of the reminder respectively
(Refer to Figure 36). As part of defensive programming, Reminder
implements the ReadOnlyReminder
interface, which only allows read operations, thus
helping to prevent inappropriate or unintended modifications to Reminder
objects (Refer to Figure 36) during the execution of the program.
For example, for DeleteReminderCommand
, its execute
method should only execute the deletion of a specified reminder, not to alter any reminders.
To ensure that no unintended or inappropriate alteration happens, DeleteReminderCommand
is passed a list of ReadOnlyReminder
instead of Reminder
. This can
be seen from the code snippet below:
public class DeleteReminderCommand extends Command {
@Override
public CommandResult execute() throws CommandException {
List<ReadOnlyReminder> reminderListing = model.getSortedReminderList();
if (targetIndex.getZeroBased() >= reminderListing.size()) {
throw new CommandException(Messages.MESSAGE_INVALID_REMINDER_DISPLAYED_INDEX);
}
ReadOnlyReminder reminderToDelete = reminderListing.get(targetIndex.getZeroBased());
try {
model.deleteReminder(reminderToDelete);
} catch (ReminderNotFoundException rnfe) {
assert false : "The target reminder cannot be missing";
}
... //return from method ...
}
}
Figure 37: In-memory implementation of Reminder.
In terms of how Reminder
objects are kept in-memory during the execution of the program, Reminder
objects are
kept within UniqueReminderList
(Refer to Figure 37), which assures that there are no duplicate Reminder
objects. The UniqueReminderList
object is then kept and used by ModelManager
to carry out commands related to reminders while the program is running.
Similar to Reminder
, as can be seen from Figure 37 above, UniqueReminderList
implements the ReadOnlyUniqueReminderList
interface to prevent
inappropriate or unintended modifications to UniqueReminderList
during the execution of the program.
Figure 38: How Reminder is stored.
As seen from Figure 38, Reminder
objects are stored in a XML storage file in a JAXB-friendly version XmlAdaptedReminder
. Notably, the Status
object is not stored along with
the Reminder
object; it is instantiated and initialized with an appropriate value during runtime when the Reminder
object is instantiated.
When the program starts, XmlAdaptedReminder
objects are read in as XmlSerializableReminders
via XmlFileStorage
and XmlUtil
.
This can be seen below:
public class XmlFileStorage {
public static XmlSerializableReminders loadRemindersFromSaveFile(File file) throws DataConversionException, FileNotFoundException {
try{
return XmlUtil.getDataFromFile(file, XmlSerializableReminders.class);
} catch (JAXBException e) {
throw new DataConversionException(e);
}
)
}
The XmlSerializableReminders
object is then passed to UniqueReminderList
, which then converts it into a list of Reminder
objects for in-memory use.
This can be seen below:
public class UniqueReminderList implements Iterable<Reminder>, ReadOnlyUniqueReminderList {
public UniqueReminderList(ReadOnlyUniqueReminderList xmlReminders) {
requireNonNull(xmlReminders);
try {
setReminders(xmlReminders.toModelType());
} catch (DuplicateReminderException dre) {
assert false : "Reminders from storage should not have duplicates";
}
}
public void setReminders(List<ReadOnlyReminder> reminders) throws DuplicateReminderException {
final UniqueReminderList replacement = new UniqueReminderList();
for (final ReadOnlyReminder reminder : reminders) {
replacement.add(new Reminder(reminder));
}
setReminders(replacement);
}
public void setReminders(UniqueReminderList replacement) {
this.internalList.setAll(replacement.internalList);
}
}
As can be seen from the code snippet above and in Figure 38, as part of defensive programming, XmlSerializableReminders
also implements the ReadOnlyUniqueReminderList
interface.
To save, the saveReminders
method in StorageManager
is invoked:
@Override
public void saveReminders(ReadOnlyUniqueReminderList reminderList) throws IOException {
saveReminders(reminderList, remindersStorage.getRemindersFilePath());
}
@Override
public void saveReminders(ReadOnlyUniqueReminderList reminderList, String filePath) throws IOException {
logger.fine("Attempting to write to data file: " + filePath);
remindersStorage.saveReminders(reminderList, filePath);
}
This will then invoke the saveReminders
method in XmlRemindersStorage
, and go on to invoke the saveRemindersToFile
method in XmlFileStorage
,
and finally the saveDataToFile
method in XmlUtil
.
The color of the reminder cells are decided and set during the formation of ReminderListViewCell
. This can be seen from the
code snippet below:
public class BirthdayAndReminderListPanel extends UiPart<Region> {
class ReminderListViewCell extends ListCell<ReminderCard> {
@Override
protected void updateItem(ReminderCard reminder, boolean empty) {
super.updateItem(reminder, empty);
if (empty || reminder == null) {
setGraphic(null);
setText(null);
return;
}
this.getStylesheets().clear();
if (reminder.isEventToday()) {
this.getStylesheets().add(REMINDER_TODAY_STYLE_SHEET);
} else if (reminder.isEventWithinThreeDays()) {
this.getStylesheets().add(REMINDER_THREE_DAYS_STYLE_SHEET);
} else if (!reminder.hasEventPassed()) {
this.getStylesheets().add(REMINDER_NORMAL_STYLE_SHEET);
}
setGraphic(reminder.getRoot());
}
}
}
Depending on the status of the reminder, different styles would be set for the specific ReminderListViewCell
.
The methods isEventToday
, isEventWithinThreeDays
and hasEventPassed
seen from the code snippet above originate from
the Status
object of each Reminder
object and propagated from Status
to Reminder
then finally to ReminderCard
.
Design Consideration
Aspect: How to store Reminder
in-memory
Alternative 1 (current choice): Store Reminder
objects in UniqueReminderList
, independent of AddressBook
.
Pros: Follows the Single-Responsibility Principle, because a reminder should not be an address book’s responsibility.
Cons: More changes have to be made to the existing code base, making it tougher to implement.
Alternative 2: Store Reminder
objects within AddressBook
.
Pros: Easier to implement as lesser changes have to be made to the existing code base.
Cons: Violates the Single-Responsibility Principle.
Aspect: Should the undo/redo mechanism be applied to Reminder
Alternative 1 (current choice): Commands that affect Reminder
objects are not undoable/redoable.
Pros: Users would be more aware of any changes they made to a reminder, as they are forced to
use the editreminder
, addreminder
and deletereminder
commands.
Cons: It is less user-friendly.
Alternative 2: Commands that affect Reminder
objects are undoable/redoable.
Pros: More user-friendly.
Cons: Users might get careless with changing the information of a Reminder
object. This might be detrimental
as the date and time fields of a Reminder
are very important.
End of Extract
Toggling between reminders panel and browser: toggle
Toggle between the reminders panel (Refer to Figure 12 below) and the browser (Refer to Figure 13 below) as needed.
The reminders panel would display reminders for upcoming birthdays amongst the contacts in the current month and
also reminders that users can set for themselves. The birthday reminders and reminders are
displayed chronologically.
Format: toggle
Figure 12 : The reminders panel.
Figure 13 : The browser panel.
Figure 14 : The default background.
End of Extract
Justification
To make iContacts useful and appealing, it needs to be able to provide more functionality to users, and this also means that it needs
to be able to display more information. Without this toggling mechanism, users would have to squint their eyes as all the information
and the browser would be squeezed together. Hence, to ensure a much more pleasant user experience, this toggling mechanism is required,
as it allows related information to be displayed together in separate panels without being squeezed.
However, we choose to permanently display the list of contacts as we felt that iContacts is first and foremost an address book, hence the list of contacts is the most important information within iContacts and thus should always be displayed.
Toggling mechanism
The toggling mechanism is an event-driven
mechanism.
Figure 33 : Component interactions for the toggling mechanism.
The above diagram (Refer to Figure 33) shows the high-level overview of the component interactions for the toggling mechanism.
Figure 34 : Sequence diagram for the first part of the toggling mechanism.
As seen from the sequence diagram above (Refer to Figure 34), when the user types the command for toggle
, an instance of ToggleCommand
would be created.
Upon execution by LogicManager
, the event BrowserAndRemindersPanelToggleEvent
would be posted by the EventCenter
to the EventBus
:
public class ToggleCommand extends Command {
@Override
public CommandResult execute() {
EventsCenter.getInstance().post(new BrowserAndRemindersPanelToggleEvent());
// ... return some object or null ...
}
}
As seen from the diagram below (Refer to Figure 35), the method handleBrowserToggleEvent()
in the BrowserAndRemindersPanel
class in the UI
component will then listen for the event,
and upon receiving the event it will invoke the method toggleBrowserPanel
to trigger the actual toggling.
Figure 35 : Sequence diagram for the second part of the toggling mechanism.
The BrowserAndRemindersPanel
class has a variable currentlyInFront
keeping track of which panel (browser or reminders) is currently being shown.
toggleBrowserPanel
would then use the currentlyInFront
variable to toggle to and show the correct panel, and then update currentlyInFront
appropriately:
public class BrowserAndRemindersPanel extends UIPart<Region> {
@Subscribe
private void handleBrowserPanelToggleEvent(BrowserAndRemindersPanelToggleEvent event) {
logger.info(LogsCenter.getEventHandlingLogMessage(event));
toggleBrowserPanel();
}
private void toggleBrowserPanel() {
switch(currentlyInFront) {
case BROWSER:
setUpToShowRemindersPanel();
remindersPanel.toFront();
currentlyInFront = Node.REMINDERS;
break;
case REMINDERS:
setUpToShowWebBrowser();
browser.toFront();
currentlyInFront = Node.BROWSER;
break;
//... Other cases ...
}
}
}
One important thing to note is that when the select
command is executed, the browser panel would be brought forward no matter what:
public class BrowserAndRemindersPanel extends UIPart<Region> {
@Subscribe
private void handleLoadPersonPageEvent(LoadPersonWebpageEvent event) {
setUpToShowWebBrowser();
currentlyInFront = Node.BROWSER;
browser.toFront();
loadPersonPage(event.getPerson());
}
private void setUpToShowWebBrowser() {
browser.setVisible(true);
detailsPanel.setVisible(false);
remindersPanel.setVisible(false);
}
}
The LoadPersonWebpageEvent
is posted whenever the select
command is executed. Within the handleLoadPersonPageEvent
method, the statement browser.toFront()
and the method setUpToShowWebBrowser
would then bring the browser panel to the front no matter what, and also make it visible.
Design Considerations
Aspect: Usage of browser area
Alternative 1 (current choice): Allow users to toggle between the browser and reminders panel.
Pros: Can use the entire space for either the browser or reminders panel.
Cons: Users need to manually switch between the browser and reminders panel.
Alternative 2: Put reminders and browser panels side-by-side.
Pros: Users do not need to manually switch between the browser and reminders panel.
Cons: Too little space for browser and reminders, making it difficult to read for the users.
Aspect: Implementation of toggling mechanism.
Alternative 1 (current choice): Bring browser and reminders panel to the front as required, on top of setting their visibility suitably.
Pros: Users can interact with both browser and reminders (can scroll through reminders etc).
Cons: More difficult to implement.
Alternative 2: Only set visibility of browser and reminders panel as required (set browser visibility to false to display reminders and vice versa).
Pros: Easier to implement.
Cons: Users cannot interact with the reminders panel (since the browser is technically still at the front).
End of Extract
Enhancement Proposed: Find by similar keywords
The find
command in iContacts currently only works for keywords with exact wording (i.e. find Han
would not work for a contact with name Hans
),
although it is still case-insensitive. This might be very inconvenient for users, as they might not remember the exact wording. Hence,
the find
command should be enhanced such that it can work with similar keywords.
Enhancement Proposed: Dynamic command syntax validation using CommandBox border color
Currently, iContacts only notify users of invalid command syntax after users have typed out the whole command and pressed Enter
.
As the feedback to the users is very general, users might have a hard time figuring out which part of the command had an erroneous
syntax. Therefore, this dynamic command syntax validation feature would be able to instantly feedback to users regarding the
validity of the command they typed, by changing the color of the border of the CommandBox
. There will be three colors:
-
A green border means that the syntax of the command is valid.
-
A red border means that the syntax of the command is invalid.
-
A yellow border means that the syntax of the command is not wrong, but is incomplete.
Enhancement Proposed: Integration with online address book and Cloud Storage services
Currently, iContacts is not integrated with any existing online services such as Google Contacts or Google Cloud Platform. Users still have to manually export the source file to share their contacts, and this can only take place if both users use iContacts. Furthermore, if the local copies of the source file are all corrupted or deleted, there are no online backup copies. Hence, there is a need to integrate iContacts with existing online address book and Cloud Storage services.
Other contributions
-
Created the current default GUI of iContacts, as well as creating the Day theme for iContacts. (Pull requests #195)
-
Updated component diagrams in DG and diagrams in UG during the project. (Pull requests #171, #200, #201, #149, #108, #215, #242, #243)
-
Implemented Birthday field. (Pull requests #76)
-
Updated READ.ME. (Pull requests #86)
-
Updated NFR. (Pull requests #50)