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

  • All three parameters REMINDER, DATE and TIME must be filled.

  • DATE must be in the format dd/mm/yyyy. '-', '/' or '.' can be used to separate the day, month and year field of the date, and they need not be paired up (i.e. 24.03/2017 is acceptable as well). Only values from 1900 to 2099 are allowed for the year field.

  • TIME must be in 24-hr format, with a colon separating the hour and minute values. Example: 08:00, 16:00, 23:59.

  • REMINDER can be of any value, as long as it is not empty.

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

ReminderPast

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:

  • If the event has passed, the reminder cell is colored in dark grey (Refer to Figure 2).

  • If the event is happening today, the reminder cell is colored in red (Refer to Figure 3).

  • If the event is happening within three days, the reminder cell is colored in orange (Refer Figure 4).

  • If the event is happening more than three days later, the reminder cell is colored in green (Refer to Figure 5).

ReminderToday

Figure 3: A reminder of an event happening today.

ReminderThreeDays

Figure 4: A reminder of an event happening within three days.

ReminderNormal

Figure 5: A reminder of an event happening more than three days later.

  • The countdown to the event, as well as the color of the reminder cells, are not dynamic. A new count and update only takes place when the program is started up, and when an edit is made to a reminder.

  • The undo and redo commands do not work for commands that affect the reminders.

Editing a reminder: editreminder

Edits an existing reminder in iContacts.
Format: editreminder INDEX [rd/REMINDER] [d/DATE] [ti/TIME]

  • Edits the reminder at the specified INDEX. The index refers to the index number shown in the list of reminders. 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 given input values. If the field is not specified, the original value will be used instead.

Examples:

  • editreminder 1 rd/Drink coffee
    Edits the content of the 1st reminder to be Drink coffee.

  • editreminder 3 d/25/12/2017 ti/19:00
    Edits the date and time of the 3rd reminder to be 25/12/2017 and 19:00 respectively.

Deleting a reminder: deletereminder

Deletes a specified reminder from iContacts.
Format: deletereminder INDEX

  • Deletes the reminder at the specified INDEX.

  • The index refers to the index number shown in the list of reminders.

  • The index must be a positive integer 1, 2, 3, …​

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.

Implementation


Start of Extract [from: Developer Guide]

Reminder Implementation

ReminderUML1

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 ...
    }
}
ReminderUML2

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.

ReminderUML3

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


Enhancement Added: Toggle

External behavior


Start of Extract [from: User Guide]

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

  • iContacts would display the reminders panel at start up.

  • Executing the select command would always bring the browser to the front.

  • Toggling to the browser without first executing a select command would display a default background (Refer to Figure 14).

  • Executing this toggle command when the application is showing the details of a contact would always bring the reminders panel to the front, and then alternate between the reminders panel and the browser on further execution of the toggle command.

RemindersPanel

Figure 12 : The reminders panel.

BrowserPanel

Figure 13 : The browser panel.

DefaultBackground

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.

Implementation


Start of Extract [from: Developer Guide]

Toggling mechanism

The toggling mechanism is an event-driven mechanism.

togglingMechanism1

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.

togglingMechanism2

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.

togglingMechanism3

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 AboutUs.adoc. (Pull requests #88, #212, #241)

  • Updated Glossary. (Pull requests #59, #70)

  • Updated NFR. (Pull requests #50)