Skip to content

Latest commit

 

History

History
2153 lines (1572 loc) · 95.8 KB

DeveloperGuide.adoc

File metadata and controls

2153 lines (1572 loc) · 95.8 KB

My Team Manager - Developer Guide

By: F14-B1      Since: Mar 2018      Licence: MIT

1. Overview

My Team Manager (MTM) is a team managing application for football coaches. MTM is designed to be quick and easy for you to manage a team of players without the need of a mouse.

This developer’s guide provides information that will not only show the design principles of the project, but allow you to understand the different implementations, and give you the opportunity to get started with being a contributor or as a reference for experienced developer.

This developer’s guide consist of the following sections:

2. Setting up

The following pointers teach you how to get started with developing on our application. You’ll learn how to prepare yourself with the project on IntelliJ and run it. Then, you’ll update configurations in IntelliJ to easily match the coding standards and you’ll be ready to start coding.

But before you start, there are two fundamental prerequisites you should know about MTM:

2.1. Prerequisites

MTM is written in Java 8 and with the use of an integrated development environment (IDE) such as IntelliJ, it provides you as a developer with a better environment suited for developing MTM to maintain coding standards and provide continuous integration.

  1. You are required to have JDK 1.8.0_60 or later,

    ℹ️
    Having any Java 8 version is not enough.
    This app will not work with earlier versions of Java 8.
    You can download Java 8 from here.
  2. and IntelliJ IDE.

    ℹ️
    IntelliJ by default has Gradle and JavaFx plugins installed.
    Do not disable them. If you have disabled them, go to File > Settings > Plugins to re-enable them.
    You can download IntelliJ from here.

2.2. Setting up the project on your computer

Before you begin setting up the project, please ensure that you’ve done the following on our GitHub repository (repo).

  1. Fork this repo, and clone the fork to your computer

Upon completion of forking from our GitHub repo, you can proceed to set up your project on IntelliJ.

  1. Open IntelliJ (if you are not in the welcome screen, click File > Close Project to close the existing project dialog first)

  2. Set up the correct JDK version for Gradle

    1. Click Configure > Project Defaults > Project Structure

    2. Click New…​ and find the directory of the JDK

  3. Click Import Project

  4. Locate the build.gradle file and select it. Click OK

  5. Click Open as Project

  6. Click OK to accept the default settings

  7. Open a console and run the command gradlew processResources (Mac/Linux: ./gradlew processResources). It should finish with the BUILD SUCCESSFUL message which will generate all resources required by the application and tests.

2.3. Verifying the setup

Now that you’ve properly setup your project, you can verify that you have done everything properly.

  1. Run the seedu.address.MainApp and try a few commands

  2. Run the tests to ensure they all pass.

2.4. Configurations to do before writing code

You have verified the setup and you’re all ready to dive into the code, but before you do, check out the following configurations that will help you make your coding more integrated and accurate.

2.4.1. Configuring the coding style

This project follows oss-generic coding standards. IntelliJ’s default style is mostly compliant with ours but it uses a different import order from ours. To rectify,

  1. Go to File > Settings…​ (Windows/Linux), or IntelliJ IDEA > Preferences…​ (macOS)

  2. Select Editor > Code Style > Java

  3. Click on the Imports tab to set the order

    • For Class count to use import with '*' and Names count to use static import with '*': Set to 999 to prevent IntelliJ from contracting the import statements

    • For Import Layout: The order is import static all other imports, import java.*, import javax.*, import org.*, import com.*, import all other imports. Add a <blank line> between each import

Optionally, you can follow the UsingCheckstyle.adoc document to configure Intellij to check style-compliance as you write code.

2.4.2. Updating documentation to match your fork

After forking the repo, links in the documentation will still point to the se-edu/addressbook-level4 repo. If you plan to develop this as a separate product (i.e. instead of contributing to the se-edu/addressbook-level4) , you should replace the URL in the variable repoURL in DeveloperGuide.adoc and UserGuide.adoc with the URL of your fork.

2.4.3. Setting up CI

Set up Travis to perform Continuous Integration (CI) for your fork. See UsingTravis.adoc to learn how to set it up.

After setting up Travis, you can optionally set up coverage reporting for your team fork (see UsingCoveralls.adoc).

ℹ️
Coverage reporting could be useful for a team repository that hosts the final version but it is not that useful for your personal fork.

Optionally, you can set up AppVeyor as a second CI (see UsingAppVeyor.adoc).

ℹ️
Having both Travis and AppVeyor ensures your App works on both Unix-based platforms and Windows-based platforms (Travis is Unix-based and AppVeyor is Windows-based)

2.5. Getting started with coding

When you are ready to start coding,

  1. Get some sense of the overall design by reading Section 3.1, “Software Architecture”.

  2. Dive right in and get started with programming.

3. Design

After you have successfully configured MTM, you are encouraged to read through the following section to understand the design of the software. The design section covers a high level overview the architecture and components of the software, as well as how common classes, configuration and logging throughout development are being used.

3.1. Software Architecture

The Architecture Diagram given below (Figure 1) explains the high-level design of the App.

Architecture
Figure 1. Architecture Diagram

Given below is a quick overview of each component.

💡
The .pptx files used to create diagrams in this document can be found in the diagrams folder. To update a diagram, modify the diagram in the pptx file, select the objects of the diagram, and choose Save as picture.

Main has only one class called MainApp. It is responsible for,

  • At app launch: Initializes the components in the correct sequence, and connects them up with each other.

  • At shut down: Shuts down the components and invokes cleanup method where necessary.

Commons represents a collection of classes used by multiple other components. Two of those classes play important roles at the architecture level.

  • EventsCenter : This class (written using Google’s Event Bus library) is used by components to communicate with other components using events (i.e. a form of Event Driven design)

  • LogsCenter : Used by many classes to write log messages to the App’s log file.

The rest of the App consists of 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.

Each of the four components

  • Defines its API in an interface with the same name as the Component.

  • Exposes its functionality using a {Component Name}Manager class.

For example, the Logic component (see the class diagram given below) defines it’s API in the Logic.java interface and exposes its functionality using the LogicManager.java class.

LogicClassDiagram
Figure 2. Class Diagram of the Logic Component

Events-Driven nature of the design

The Sequence Diagram below shows how the components interact for the scenario where the user issues the command delete 1.

SDforDeletePerson
Figure 3. Component interactions for delete 1 command (part 1)
ℹ️
Note how the Model simply raises a AddressBookChangedEvent when the Address Book data are changed, instead of asking the Storage to save the updates to the hard disk.

The diagram below shows how the EventsCenter reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time.

SDforDeletePersonEventHandling
Figure 4. Component interactions for delete 1 command (part 2)
ℹ️
Note how the event is propagated through the EventsCenter to the Storage and UI without Model having to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct coupling between components.

The sections below give more details of each component.

3.2. UI component

The UI Component contains a MainWindow that consists of various parts e.g. CommandBox, ResultDisplayer, TeamDetails, PersonListPanel, PlayerDetails etc. The UI classes are all child classes of the abstract UiPart class.

The structure of the UI component is shown in the figure below:

UiClassDiagram
Figure 5. Structure of the UI Component

API : Ui.java

The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, PersonListPanel, StatusBarFooter, PlayerDetails etc. All these, including the MainWindow, inherit from the abstract UiPart class.

The UI component uses 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,

  • Executes user commands using the Logic component.

  • Binds itself to some data in the Model so that the UI can auto-update when data in the Model change.

  • Responds to events raised from various parts of the App and updates the UI accordingly.

3.3. Logic component

The Logic Component handles how each command would be parsed and executed. The class diagrams below illustrates the structure of the Logic Component and the structure of each individual commands.

LogicClassDiagram
Figure 6. Structure of the Logic Component
LogicCommandClassDiagram
Figure 7. Structure of Commands in the Logic Component. This diagram shows finer details concerning XYZCommand and Command in Figure 6, “Structure of the Logic Component”

API : Logic.java

  1. Logic uses the AddressBookParser class to parse the user command.

  2. This results in a Command object which is executed by the LogicManager.

  3. The command execution can affect the Model (e.g. adding a person) and/or raise events.

  4. The result of the command execution is encapsulated as a CommandResult object which is passed back to the Ui.

Given below is the Sequence Diagram for interactions within the Logic component for the execute("delete 1") API call.

DeletePersonSdForLogic
Figure 8. Interactions Inside the Logic Component for the delete 1 Command

3.4. Model component

The Model Component handles the players and teams data structures in My Team Manager. These structures also provide APIs to create, read, update and delete the details of these objects. The class diagram below shows the structure of the Model Component.

ModelClassDiagram
Figure 9. Structure of the Model Component

API : Model.java

The Model,

  • stores a UserPref object that represents the user’s preferences.

  • stores the Address Book data.

  • exposes 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.

  • does not depend on any of the other three components.

3.5. Storage component

Any changes made to the data is retained and handled by the Storage Component. The structure of this component is shown in Figure 10 below.

StorageClassDiagram
Figure 10. Structure of the Storage Component

API : Storage.java

The Storage component,

  • can save UserPref objects in json format and read it back.

  • can save the Address Book data in xml format and read it back.

3.6. Common classes

All classes used by the different components can be found in the seedu.addressbook.commons package.

3.7. Configuration

Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file (default: config.json).

3.8. Logging

We are using java.util.logging package for logging. The LogsCenter class is used to manage the logging levels and logging destinations.

  • The logging level can be controlled using the logLevel setting in the configuration file (See Configuration)

  • The Logger for a class can be obtained using LogsCenter.getLogger(Class) which will log messages according to the specified logging level

  • Currently log messages are output through: Console and to a .log file.

Logging Levels

  • SEVERE : Critical problem detected which may possibly cause the termination of the application

  • WARNING : Can continue, but with caution

  • INFO : Information showing the noteworthy actions by the App

  • FINE : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size

4. Implementation

This section describes some noteworthy details on how certain features are implemented.

4.1. Pre-existing

4.1.1. Undo/Redo feature

Current Implementation

The undo/redo mechanism is facilitated by an UndoRedoStack, which resides inside LogicManager. It supports undoing and redoing of commands that modifies the state of the address book (e.g. add, edit). Such commands will inherit from UndoableCommand.

UndoRedoStack only deals with UndoableCommands. Commands that cannot be undone will inherit from Command instead. The following diagram shows the inheritance diagram for commands:

LogicCommandClassDiagram
Figure 11. Logic Class Diagram

As you can see from the diagram, UndoableCommand adds an extra layer between the abstract Command class and concrete commands that can be undone, such as the DeleteCommand. Note that extra tasks need to be done when executing a command in an undoable way, such as saving the state of the address book before execution. UndoableCommand contains the high-level algorithm for those extra tasks while the child classes implements the details of how to execute the specific command. Note that this technique of putting the high-level algorithm in the parent class and lower-level steps of the algorithm in child classes is also known as the template pattern.

Commands that are not undoable are implemented this way:

public class ListCommand extends Command {
    @Override
    public CommandResult execute() {
        // ... list logic ...
    }
}

With the extra layer, the commands that are undoable are implemented this way:

public abstract class UndoableCommand extends Command {
    @Override
    public CommandResult execute() {
        // ... undo logic ...

        executeUndoableCommand();
    }
}

public class DeleteCommand extends UndoableCommand {
    @Override
    public CommandResult executeUndoableCommand() {
        // ... delete logic ...
    }
}

Suppose that the user has just launched the application. The UndoRedoStack will be empty at the beginning.

The user executes a new UndoableCommand, delete 5, to delete the 5th person in the address book. The current state of the address book is saved before the delete 5 command executes. The delete 5 command will then be pushed onto the undoStack (the current state is saved together with the command).

UndoRedoStartingStackDiagram
Figure 12. Undo/Redo Stack Diagram (Part 1)

As the user continues to use the program, more commands are added into the undoStack. For example, the user may execute add n/David …​ to add a new person.

UndoRedoNewCommand1StackDiagram
Figure 13. Undo/Redo New Command Stack Diagram (Part 1)
ℹ️
If a command fails its execution, it will not be pushed to the UndoRedoStack at all.

The user now decides that adding the person was a mistake, and decides to undo that action using undo.

We will pop the most recent command out of the undoStack and push it back to the redoStack. We will restore the address book to the state before the add command executed.

UndoRedoExecuteUndoStackDiagram
Figure 14. Undo/Redo Stack Diagram (Part 2)
ℹ️
If the undoStack is empty, then there are no other commands left to be undone, and an Exception will be thrown when popping the undoStack.

The following sequence diagram shows how the undo operation works:

UndoRedoSequenceDiagram
Figure 15. Undo/Redo Sequence Diagram

The redo does the exact opposite (pops from redoStack, push to undoStack, and restores the address book to the state after the command is executed).

ℹ️
If the redoStack is empty, then there are no other commands left to be redone, and an Exception will be thrown when popping the redoStack.

The user now decides to execute a new command, clear. As before, clear will be pushed into the undoStack. This time the redoStack is no longer empty. It will be purged as it no longer make sense to redo the add n/David command (this is the behavior that most modern desktop applications follow).

UndoRedoNewCommand2StackDiagram
Figure 16. Undo/Redo New Command Stack Diagram (Part 2)

Commands that are not undoable are not added into the undoStack. For example, list, which inherits from Command rather than UndoableCommand, will not be added after execution:

UndoRedoNewCommand3StackDiagram
Figure 17. Undo/Redo New Command Stack Diagram (Part 3)

The following activity diagram summarize what happens inside the UndoRedoStack when a user executes a new command:

UndoRedoActivityDiagram
Figure 18. Undo/Redo Activity Diagram
Design Considerations
Aspect: Implementation of UndoableCommand
  • Alternative 1 (current choice): Add a new abstract method executeUndoableCommand().

    • Pros: We will not lose any undone/redone functionality as it is now part of the default behaviour. Classes that deal with Command do not have to know that executeUndoableCommand() exist.

    • Cons: Hard for new developers to understand the template pattern.

  • Alternative 2: Just override execute().

    • Pros: Does not involve the template pattern, easier for new developers to understand.

    • Cons: Classes that inherit from UndoableCommand must remember to call super.execute(), or lose the ability to undo/redo.

Aspect: How undo & redo executes
  • Alternative 1 (current choice): Saves the entire address book.

    • Pros: Easy to implement.

    • Cons: May have performance issues in terms of memory usage.

  • Alternative 2: Individual command knows how to undo/redo by itself.

    • Pros: Will use less memory (e.g. for delete, just save the person being deleted).

    • Cons: We must ensure that the implementation of each individual command are correct.

Aspect: Type of commands that can be undone/redone
  • Alternative 1 (current choice): Only include commands that modifies the address book (add, clear, edit).

    • Pros: We only revert changes that are hard to change back (the view can easily be re-modified as no data are * lost).

    • Cons: User might think that undo also applies when the list is modified (undoing filtering for example), * only to realize that it does not do that, after executing undo.

  • Alternative 2: Include all commands.

    • Pros: Might be more intuitive for the user.

    • Cons: User have no way of skipping such commands if he or she just want to reset the state of the address * book and not the view. Additional Info: See our discussion here.

Aspect: Data structure to support the undo/redo commands
  • Alternative 1 (current choice): Use separate stack for undo and redo.

    • Pros: Easy to understand for new Computer Science student undergraduates to understand, who are likely to be * the new incoming developers of our project.

    • Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update * both HistoryManager and UndoRedoStack.

  • Alternative 2: Use HistoryManager for undo/redo.

    • Pros: We do not need to maintain a separate stack, and just reuse what is already in the codebase.

    • Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as HistoryManager now needs to do two * different things.

4.2.1. Player Component [since v1.3]

Player is one of the core components of MTM. To implement it, we used the same Person object and added additional attributes to it. Shown below is the new constructor for the Person object followed by the class diagram.

public Person(Name name, Phone phone, Email email, Address address, Remark remark, TeamName teamName,
                  Set<Tag> tags, Rating rating, Position position, JerseyNumber jerseyNumber, Avatar avatar) {
        ...
        this.remark = remark;
        this.teamName = teamName;
        this.rating = rating;
        this.position = position;
        this.jerseyNumber = jerseyNumber;
        this.avatar = avatar;
    }
PlayerClassDiagram
Figure 19. Person Class Diagram

The additional attributes are: teamName, Remark, Rating, Position, Jersey Number and Avatar.

The add and edit command has also been updated to include teamName,Rating, Position, Jersey Number and Avatar. One thing to note is that in executeUndoableCommand() in both add and edit commands now does two additional things, setting the file path for players avatar, and assigning a player to a team and adding person to current filtered list. Shown below is the code snippet of the new executeUndoableCommand() and sequence diagram of the new add command as an example.

 @Override
    public CommandResult executeUndoableCommand() throws CommandException {
        requireNonNull(model);
        try {
            if (!toAdd.getAvatar().toString().equals(UNSPECIFIED_FIELD)) {
                toAdd.getAvatar().setFilePath(toAdd.getName().fullName);
            }
            model.addPerson(toAdd);
            if (!toAdd.getTeamName().toString().equals(UNSPECIFIED_FIELD)) {
                model.assignPersonToTeam(toAdd, toAdd.getTeamName());
                model.updateFilteredPersonList(toAdd.getTeamName());
                EventsCenter.getInstance().post(new HighlightSelectedTeamEvent(toAdd.getTeamName().toString()));
            } else { ...
AddSequenceDiagram
Figure 20. Add Sequence Diagram
Rating Class

The Rating class contains a String value. value holds the players rating, an integer from 0 to 5. It uses a validation regex [0-5] to ensure valid input.

The prefix for Rating is ra/

Position Class

The`Position` class contains a String value. value here holds the players position, an integer from 1 to 4. It uses a validation regex [1-4] to ensure valid input.

The prefix for Position is po/.

A static hashmap is used to store and retrieve the corresponding position names of the position values. The position name can be called using the method getPositionName() which returns myMap.get(value). The code snippet for the hashmap and getPositionName() is shown below.

private static final Map<String, String> myMap;
static {
    Map<String, String> aMap = new HashMap<>();
    aMap.put("1", "Striker");
    aMap.put("2", "Midfielder");
    aMap.put("3", "Defender");
    aMap.put("4", "Goalkeeper");
    myMap = Collections.unmodifiableMap(aMap);
}

public String getPositionName() {
    return myMap.get(value);
}
JerseyNumber Class

The`JerseyNumber` class contains a String value. value here holds the players jersey number, an integer from 0 to 99. It uses a validation regex [0-9]|[1-8][0-9]|9[0-9] to ensure valid input.

The prefix for Position is j/.

Avatar Class

The`Avatar` class contains a String value. value here holds the absolute filepath to the players avatar image file, a png of jpeg file. It uses a validation regex ([^\s]+(\.(?i)(jpg|png))$) to ensure valid input.

The prefix for Avatar is av/.

When the class is created, the value stores the original location of the players avatar image file eg. C:\image.png or if not specified, stores "<UNSPECIFIED>". Thereafter, if the avatar is specified when adding or editing a player, setFilePath() is called which will attempt to copy the image file to a /image folder that is in the same directory as the MTM.jar file. value is then updated to the new file path.

The code snippet for setFilePath() is shown below.

public void setFilePath(String player) throws IOException {
        if (value.equals("<UNSPECIFIED>")) {
            return;
        }
        final File file = new File(value);

        Path dest = new File("images/" + player.replaceAll("\\s+", "") + ".png").toPath();
        Files.createDirectories(Paths.get("images")); // Creates missing directories if any
        Files.copy(file.toPath(), dest, StandardCopyOption.REPLACE_EXISTING);
        this.value = dest.toString();
    }

4.2.2. Remark a Player [since v1.1]

Leaving a remark would be ideal for the user to note down any important detail of a player that is useful in the future. Remarks given can be in any format and therefore would not look good if it was done using tags instead, as tags are used with minimal words.

Current Implementation

The remark mechanism is facilitated by RemarkCommand and it inherits from UndoableCommand, making it undoable. The mechanism allows user to perform the adding, editing, and deleting of a single remark to a specified Person via the INDEX from the list shown in the UI. The field remark is similar to the other fields of Person, hence some of its logic in AddCommand.java and EditCommand.java are updated.

The mechanism uses the command remark and a r/ prefix to add, edit, and delete a single remark of a Person. When the user leaves the remark as empty after the r/ prefix, it is an indication to delete the remark, and when it is valid, it either create a new remark for that Person or overwrites the current existing remark. Only one remark is saved at a time. If the user remark on the same person, it will be overwritten.

The field remark is found in Person, and it is not modified via add or edit commands. All functions related to remark is done strictly via the remark command.

The sequence diagram below illustrates the operation of the remark command:

RemarkActivityDiagram
Figure 21. Remark Activity Diagram

The remark field is not required when adding or editing a Person, and it will be initialized to an empty string or retrieved to fit the implementation of the other fields.

Code snippet from AddCommandParser.java that shows how remark is initialized as empty.

public AddCommand parse(String args) throws ParseException {
    //...AddCommandParser code...
    Remark remark = new Remark("");

    Person person = new Person(name, phone, email, address, remark, teamName, tagList, rating,
        position, jerseyNumber, avatar);
    //...AddCommandParser code...
}

Code snippet from EditCommand.java that shows how remark is being retrieved.

private static Person createEditedPerson(Person personToEdit,
    EditPersonDescriptor editPersonDescriptor) {
    //...EditCommand code...
    Remark updatedRemark = (personToEdit.getRemark().isPrivate()) ? personToEdit.getRemark()
                                                                  : personToEdit.getRemark();

    return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedRemark,
        updatedTeamName, updatedTags, updatedRating, updatedPosition, updatedJerseyNumber,
        updatedAvatar);
}
Design Considerations
Aspect: Implementation of Remark field
  • Alternative 1 (current choice): Creates an additional remark field in Person.

    • Pros: Follows the existing style of similar fields, making implementation easier.

    • Cons: Could only have 1 remark per person, unless remark mechanism follows the tag mechanism to have multiple remarks.

  • Alternative 2: Have a list of remarks that associates with the specified Person.

    • Pros: Can have multiple remarks associating with a specific Person.

    • Cons: Harder to maintain, more complicated.

Aspect: Implementation of Remark command
  • Alternative 1 (current choice): Create a specific command for remarking Person.

    • Pros: Optional for user to enter remark during add, and have an isolated command to give remarks to a player that works as an add and edit command for remark.

    • Cons: Increases the number of commands available for the user, and could mistaken that remark can be done using add/edit command.

  • Alternative 2: Make it similar to how other fields are implemented in Person.

    • Pros: Quick and easy implementation as it uses the same format as other fields, and can be added and modified through add and edit commands.

    • Cons: An additional field that can be input when adding a player, making the process longer to execute the command.

4.2.3. Setting various player fields to private [since v1.4]

Current Implementation

Phone, Email, Remark, Rating and Address of a player has an additional boolean attribute isPrivate which tracks the privacy of the particular field. When a player is added into MTM, isPrivate of these fields are set to 'false' by default.

Each of these fields have a different toString method which would return <Private 'FIELD'> if privacy of the field is set to 'true', as shown in the following code snippet:

@Override
    public String toString() {
        if (isPrivate) {
            return "<Private Address>";
        }
        return value;
    }

XmlAdaptedPerson under the storage portion has been edited to save the isPrivate value for the fields:

@XmlElement(required = true)
    private Boolean phonePrivacy;

Toggling of privacy works in a similar way to Edit. New fields will be created with privacy settings of EditPersonPrivacy based on user input. This is done by first detecting if a particular prefix is present when command is entered in TogglePrivacyCommandParser:

if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
            epp.setPrivatePhone(false);
        }

If prefix of a field is not present, the privacy setting of that particular field in EditPersonPrivacy would be null.

In TogglePrivacyCommand, the new fields are created with this:

private static Phone createPhonePrivacy(Person person, EditPersonPrivacy epp) {
        Phone phone;
        try {
            if (person.getPhone().isPrivate()) {
                person.getPhone().togglePrivacy();
                phone = new Phone(person.getPhone().toString());
                person.getPhone().togglePrivacy();
            } else {
                phone = new Phone(person.getPhone().toString());
            }
        } catch (Exception e) {
            throw new AssertionError("Invalid Phone");
        }
        if (epp.getPrivatePhone() != null) {
            phone.setPrivate(person.getPhone().isPrivate());
            phone.togglePrivacy();
        } else {
            phone.setPrivate(person.getPhone().isPrivate());
        }

        return phone;
    }

Note that in the second portion of the code, if epp.getPrivatePhone() is null, this would mean that it’s privacy setting was not toggled and thus is set to the same as it was before. Else, it would be toggled:

public void togglePrivacy() {
        this.isPrivate = isPrivate ? false : true;
    }

A new person object is then created:

private static Person createEditedPrivacyPerson(Person personToEdit, EditPersonPrivacy epp)
            throws IllegalValueException {
        assert personToEdit != null;

        Name updatedName = personToEdit.getName();
        Phone updatedPhone = createPhonePrivacy(personToEdit, epp);
        Email updatedEmail = createEmailPrivacy(personToEdit, epp);
        Address updatedAddress = createAddressPrivacy(personToEdit, epp);
        Remark updatedRemark = createRemarkPrivacy(personToEdit, epp);
        TeamName updatedTeamName = personToEdit.getTeamName();
        Set<Tag> updatedTags = personToEdit.getTags();
        Rating updatedRating = createRatingPrivacy(personToEdit, epp);
        Position updatedPosition = personToEdit.getPosition();
        JerseyNumber updatedJerseyNumber = personToEdit.getJerseyNumber();
        Avatar updatedAvatar = personToEdit.getAvatar();

        return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedRemark,
                updatedTeamName, updatedTags, updatedRating, updatedPosition, updatedJerseyNumber, updatedAvatar);
    }

and is used to update current person:

model.updatePerson(personToEdit, editedPerson);
Design Consideration
Aspect: How field privacy is implemented
  • Alternative 1 (current choice): Boolean isPrivate added to field classes.

    • Pros: Privacy settings can be obtained straight from class by calling getter method.

    • Cons: Adddtional methods are needed to set and get value of isPrivate.

  • Alternative 2: Field privacy settings stored in Person in a HashMap.

    • Pros: Field privacy can be accessed and modified easily

    • Cons: Person has to be accessed every time field privacy needs to be checked

Aspect: Privacy of fields upon adding of player
  • Alternative 1 (current choice): All newly added players' fields are not private by default.

    • Pros: Add command does not need to be tweaked to allow adding player with private fields.

    • Cons: After adding players, user has to do additional command to toggle privacy of fields.

  • Alternative 2: Include implementation of prefixes that denotes private field during Add.

    • Pros: Newly added players can have private fields right away.

    • Cons: Addtional changes has to be made to Add command.

4.2.4. Sorting players by fields [since v1.2]

Current Implementation

The sort command is currently able to sort players by name, email, address, rating, jersey and postition in either ascending or descending order. Support for more fields will be added in subsequent updates.

  • The sort command is parsed through SortCommandParser which hands control to the SortCommand class.

  • Java Collections Sort API is used together with a custom Comparator in this implementation

Sorting is facilitated by the SortCommand which uses method sortPlayers to ultimately call method sortBy in UniquePersonList for the actual sorting as shown in this code snippet:

        switch (field) {
                case "name":
                    comparator = nameComparator;
                    break;

                //... code for other switch cases...

                default:
                    throw new AssertionError("Invalid field parameter entered...\n");
                }

        switch (order) {
        case "asc":
            Collections.sort(internalList, comparator);
            break;

        case "desc":
            Collections.sort(internalList, Collections.reverseOrder(comparator));
            break;

        default:
            throw new AssertionError("Invalid field parameter entered...\n");
        }
    }

The code above utilises a custom Comparator defined in the sample code below:

        Comparator<Person> nameComparator = new Comparator<Person>() {
                    @Override
                    public int compare(Person p1, Person p2) {
                        return p1.getName().fullName.compareTo(p2.getName().fullName);
                    }
                };

The following sequence diagram shows the program flow when sort is used:

sortSeq
Figure 22. Sort Sequence Diagram
ℹ️
If address book is empty, a NoPlayerException will be thrown by sortBy method in UniquePlayerList.
Design Consideration
Aspect: Implementation of sort
  • Alternative 1 (current choice): Sorting is done in UniquePersonList.

    • Pros: Sorting methods resides in class that handles most operations done to list. Future changes to implementation would be easier.

    • Cons: Complicated flow of control passed between classes.

  • Alternative 2: Sorting is done in SortCommand.

    • Pros: Easier to trace flow of control as lesser passing between classes.

    • Cons: Unnecessary coupling if UniquePersonList has to rely on SortCommand.

Aspect: Sort by multiple fields
  • Alternative 1 (current choice): Sort can only be done by 1 field.

    • Pros: Easy to implement and input is straightforward.

    • Cons: Unable to fine tune to great detail how teams are sorted and displayed.

  • Alternative 2: Sort can be done by multiple fields.

    • Pros: Able to fine tune to great detail how teams are sorted and displayed.

    • Cons: Unnecessary as team managers would not need to sort players by multiple fields.

Aspect: Sort Persistence
  • Alternative 1 (current choice): Address book is saved after sorting.

    • Pros: Easy to implement. Allows team managers to use preferred sort pattern in every session.

    • Cons: Previous order of players will be lost.

  • Alternative 2: Address book is not saved after sorting.

    • Pros: Good if team manager wants to sort players for current session only.

    • Cons: Sort order is lost when program exits. More memory is used to sort a list and reverse it after.

4.3.1. Team Component [since v1.2]

One of the core components to MTM is Team, and it is the base to creating more commands for the user to access capability on team management. The Team component provides user with better organizational methods for organizing their players and interact with multiple teams to manage them effectively.

Current Implementation

We achieved the implementation of Team component by introducing a UniqueTeamList into the application which consists of a list of Team objects. UniqueTeamList is similar in context to UniquePersonList in which it keeps a unique list of all the Team objects. Team objects consist of TeamName object, which is used to uniquely identify the team. Team inherits UniquePersonList object which stores the a list of unique Person objects, containing information of the player.

The class diagrams below shoes the relationship between the related classes. The Team Class Diagrams are an extension to the current existing Designs of Model, Storage, and UI.

TeamModelClassDiagram
Figure 23. Model Component: Team Class Diagram
TeamStorageClassDiagram
Figure 24. Storage Component: Team Class Diagram
TeamUiClassDiagram
Figure 25. UI Component: Team Class Diagram

Code snippet from Team.java that shows the constructor for Team.

public class Team {
    private final TeamName teamName;

    public Team(TeamName teamName) {
        this.teamName = teamName;
    }
}

For the application to store the information of Team into a list, we will need to introduce a new UniqueTeamList variable into the AddressBook.java, so that it will have a container for all the new Team objects that is going to be created in MTM.

Code snippet from AddressBook.java that shows the declaration and initialization of UniqueTeamList.

public class AddressBook implements ReadOnlyAddressBook {
    //...AddressBook code...
    private final UniqueTeamList teams;

    {
        teams = new UniqueTeamList;
    }

    public void setTeams(List<Team> teams) throws DuplicateTeamException {
        this.teams.setTeams(teams);
    }

    @Override
    public ObservableList<Team> getTeamList() {
        return teams.asObservableList();
    }
    //...AddressBook code...
}

Since Team inherits the UniquePersonList class, we are able to store Person object into Team by using the super class method add in UniquePersonList. By storing a list of players inside Team object, future enhancements can easily make use of the data to perform functions that requires quick access to all players in a team. However, this have created a coupling in which data made to the changes made to `AddressBook.persons needs to be updated in Team to maintain synchronicity.

Code snippet from AddressBook.java that shows the propagation of details updated in Person from the full player list to the Person objects in Team.

public void updatePerson(Person target, Person editedPerson)
    throws DuplicatePersonException, PersonNotFoundException {
    requireNonNull(editedPerson);
    //...syncEditedPerson initialisation...

    if (!editedPerson.getTeamName().toString().equals(UNSPECIFIED_FIELD)) {
        teams.getTeam(editedPerson.getTeamName()).setPerson(target, editedPerson);
    }
    persons.setPerson(target, syncedEditedPerson);
}

An additional TeamName field is also added to Person so that the user can quickly determine the team the player is in through the use of PREDICATE.

Code snippet from Person.java that shows the TeamName field.

public class Person {

    private final TeamName teamName;

    public Person(Name name, Phone phone, Email email, Address address, Remark remark,
        TeamName teamName, Set<Tag> tags, Rating rating, Position position,
        JerseyNumber jerseyNumber, Avatar avatar) {
        //...attributes initialisation...
        this.teamName = teamName;
    }

    public TeamName getTeamName() {
        return teamName;
    }
}

Code snippet from ModelManager.java that shows the use of filtering the person list using the TeamName field in Person.

@Override
public void updateFilteredPersonList(TeamName targetTeam) throws TeamNotFoundException {
    requireNonNull(targetTeam);

    List<Team> teamList = addressBook.getTeamList();

    if (teamList.stream().anyMatch(target -> target.getTeamName().equals(targetTeam))) {
        filteredPersons.setPredicate(t -> t.getTeamName().equals(targetTeam));
    } else {
        throw new TeamNotFoundException();
    }
}

Additional exception classes are also created so that these new exceptions can be thrown during the program, such that when these exceptions are thrown, the code is easily comprehensible.

TeamNotFoundException.java
public class TeamNotFoundException extends Exception {
    //...TeamNotFoundException...
}
DupliecateTeamException.java
public class DuplicateTeamException extends DuplicateDataException {
    //...DuplicateTeamException...
}

Implementing a new component into MTM, the data consisting of Team is also stored into storage using XmlAdaptedTeam.java to format the output.

Code snippet from XmlAdaptedTeam.java that shows the elements that will be stored into storage.

public class XmlAdaptedTeam {
    @XmlElement(required = true)
    private String teamName;
    @XmlElement
    private List<XmlAdaptedPerson> players = new ArrayList<>();
}
Design Considerations
Aspect: Implementation of Team
  • Alternative 1 (current choice): Team object stores a Person object that is also in UniquePersonList and TeamName is also an attribute of Person.

    • Pros: Provides an easier lookup of person belonging in which team without the need of iterating through the UniqueTeamList for a specific Person, and provide ease of access to data for future enhancements.

    • Cons: Checks to ensure synchronicity need to be done thoroughly to ensure that player information is in sync between Team and full player list

  • Alternative 2: Team object is stored in Person object as an attribute.

    • Pros: Logical thought process to include Team as an attribute.

    • Cons: Excess storage wasted due to duplicated data of Team in every Person.

4.3.2. Team Functions [since v1.4]

To assist the user with manging teams, we have decided to use the design concept of Create-Read-Update-Delete (CRUD) to implement commands related to Team. This set of commands provide the basic necessities for the user to efficiently use our application.

Current Implementation

Based on the principles of CRUD, we have created the commands that corresponds to each principles, namely, create, view, rename and assign, remove. With these commands, the application is ready to support the features that the application is designed to be used.

Team-related commands that modifies the data in the application, such as create, rename, assign, and remove, inherits from UndoableCommand class which makes all these commands undoable. Each command has its own Parser to uniquely parse user input arguments for the commands, so that it is validated and the command understand what it should be doing.

For each feature, new functions are created in AddressBook.java and ModelManager.java so that the commands executed are able to manipulate the data accordingly.

After the creation of a new team, the user will then be able to perform a new set of commands that performs team management functionality. Moreover, the add command can be used with the prefix tm/ to immediately add the user into MTM and into the team specified.

The sequence diagram below illustrates the operation of the create command:

TeamCreateSequenceDiagram1
Figure 26. Logic Component: Create Sequence Diagram
TeamCreateSequenceDiagram2
Figure 27. Model Component: Create Sequence Diagram
view: Views a Team, which shows all players in the team.

The command uses the filteredPersons list, and an overloaded updateFilteredPersonList method that sets a predicate that filters the list based on a TeamName, in ModelManager.java so that it can be displayed via the UI.

The sequence diagram below illustrates the operation of the view command:

TeamViewSequenceDiagram
Figure 28. View Sequence Diagram
rename: Renames a given Team.

The user can rename a specified team to a new team name only if the current team name does not existing in MTM. On a side note, it prevents the user from renaming the team to the same name that it currently have.

This command will update the name of the teams that are in teams in AddressBook.java and will update all the players TeamName field in persons and Team with the new team name.

The sequence diagram below illustrates the operation of the rename command:

TeamRenameSequenceDiagram1
Figure 29. Logic Component: Rename Sequence Diagram
TeamRenameSequenceDiagram2
Figure 30. Model Component: Rename Sequence Diagram
assign: Assign a set of Person to a specified Team.

The core feature that organizes the player into teams so that the user can easily view the desired team players quickly. The command have 2 functions, firstly it is able to assign an individual or a set of players to a specified team, and secondly, it is able to unassign an individual or a set of players from any team.

The command takes in a set of indexes that corresponds to the player in the current list, and will perform the assign operation in ascending index order. If the command is unable to process an index given, it will process all the valid index until the index that causes an issue.

ℹ️

Typical issues that the command handles are,

  • Assigning a player to the same team that it is currently in.

  • Assigning a player to team that does not exist.

  • Unassigning a player that does not exist in any team.

  • Process index starting from 1.

  • Removes all index that exceeds the current number of player listed.

This command will update and synchronise all affected players in persons and in the specified Team object, such that their TeamName field in Person are updated with the new assigned or unassigned team. At the same time, the list of players in Team gets update if new player are assigned or unassigned.

The sequence diagram below illustrates the operation of the assign command:

TeamAssignSequenceDiagram
Figure 31. Assign Sequence Diagram
remove: Removes the given Team.

The final step in completing the CRUD design is the removal of team. It will delete the Team from teams list in AddressBook.java and will update all affected players in the team to be unassigned from the team by having their TeamName field updated with an unspecifed field.

The sequence diagram below illustrates the operation of the remove command:

TeamRemoveSequenceDiagram1
Figure 32. Logic Component: Remove Sequence Diagram
TeamRemoveSequenceDiagram2
Figure 33. Model Component: Remove Sequence Diagram
Design Consideration
Aspect: Implementation of Team Functions
  • Alternative 1 (current choice): Uses CRUD design concept to create new commands.

    • Pros: Systematic approach in dealing with what functions should be created to ensure that the product has the features required to perform team management, and provides a platform to work on ideal features that target the needs of the audience.

    • Cons: Simple and only captures the basic requirements of the product.

  • Alternative 2: Implement commands based on suggested features.

    • Pros: End product will have features that targets the need of the audiences.

    • Cons: Possible to miss out basic and core features of a team management application.

Aspect: Undoable commands for Team Functions
  • Alternative 1 (current choice): Make all functions that modifies data to be undoable.

    • Pros: Follows the current implementation of undo such that if data are modified in MTM, it will be an undoable command.

    • Cons: Need to ensure that regression bugs are squashed when the new commands are executed, and time consuming in identifying regression.

  • Alternative 2: Make all functions not undoable.

    • Pros: Easy to implement and will produce lesser regression bugs.

    • Cons: Does not align with the current implementation of undo & redo, and the user would require more steps to revert any changes made.

4.3.3. Show Best XI [coming in v2.0]

Current Implementation

The showBest command is able to show the current team’s best 11 players. There is however no functional implementation for this feature yet. Coming soon!

4.4. Usability Enhancements

4.4.1. Toggle lock on MTM [since v1.5]

Current Implementation

Locking mechanism of MTM can be toggled on and off using the Key command. It utilises the Model to access user preferences of MTM. Current lock state and password is stored in UserPrefs in the Storage component.

This activity diagram shows the logic behind the Key command:

Keyactdiag
Figure 34. Key Activity Diagram

Password checking done in Key command:

private boolean correctPassword() {
        UserPrefs up = model.getUserPrefs();
        String hash = Hashing.sha256().hashString(password, StandardCharsets.UTF_8).toString();
        return hash.equals(up.getAddressBookHashedPass());
    }

When password check is done, the lock on MTM is toggled to the state opposite of the current:

if (correctPassword()) {
            if (model.getLockState()) {
                model.unlockAddressBookModel();
            } else {
                model.lockAddressBookModel();
            }

            logger.info("Lock state is now: " + Boolean.toString(model.getLockState()));
            return new CommandResult(MESSAGE_SUCCESS);
        }

In a locked state, only certain functions of MTM can be used. This is to prevent unauthorised tampering with the details stored on MTM.

This is done in AddressBookParser. Commands are split into low level or not. When a command is being executed, it is checked if it falls under the low level category. If so, it would execute. When adding new features, add them to this list should you feel it is 'low level':

private Command lowLevelCommand(String commandWord, String arguments) throws ParseException {
        switch(commandWord) {
        case ChangeThemeCommand.COMMAND_WORD:
        case ChangeThemeCommand.COMMAND_ALIAS:
            return new ChangeThemeCommandParser().parse(arguments);

        case FindCommand.COMMAND_WORD:
        case FindCommand.COMMAND_ALIAS:
            return new FindCommandParser().parse(arguments);

        case ListCommand.COMMAND_WORD:
        case ListCommand.COMMAND_ALIAS:
            return new ListCommand();

        case KeyCommand.COMMAND_WORD:
        case KeyCommand.COMMAND_ALIAS:
            return new KeyCommandParser().parse(arguments);

        case ViewCommand.COMMAND_WORD:
        case ViewCommand.COMMAND_ALIAS:
            return new ViewCommandParser().parse(arguments);

        case ExitCommand.COMMAND_WORD:
            return new ExitCommand();

        case HelpCommand.COMMAND_WORD:
            return new HelpCommand();

        case SortCommand.COMMAND_WORD:
        case SortCommand.COMMAND_ALIAS:
            return new SortCommandParser().parse(arguments);

        default:
            return null;
        }
    }

If command being executed does not fall into the category of 'low level', a check on the lock state of MTM is done before allowing or restricting access:

if (lockState) {
            throw new ParseException(MESSAGE_RESTRICTED);
        }
Design Considerations
Aspect: Implementation of locking MTM
  • Alternative 1 (current choice): Lock is toggled with only a single command Key.

    • Pros: Easy to implement, just check current lock state and switch it.

    • Cons: With a toggle, user might unlock MTM thinking he/she is locking it.

  • Alternative 2: Locking and unlocking of MTM is done with two separate commands.

    • Pros: Ensures that when a lock is done, MTM is truly locked.

    • Cons: Addtional command needs to be created, along with its command parser and implementations.

4.4.2. Change Password [coming in v2.0]

Current Implementation

Default password used for toggling lock on MTM is currently 'ilikesports'. In an upcoming update, user would be able to change this password to one of his choosing. There is currently no functional implementation for this feature yet. Coming soon!

4.5. User Experience Enhancements

4.5.1. Tab Autocomplete

Current Implementation

The tab autocomplete feature works by handling the Tab key pressed event, searching for any commands with matching prefix and returns one if found. The command strings are stored in a Trie data structure, named CommandTrie, for optimal search.

public class CommandTrie {
    String attemptAutoComplete (String input);
    void insert (String input);
    Set<String> getCommandSet();
}

The trie is made up of TrieNode objects which contains three objects. The TrieNode sibling represents a character on the same level as the current one. The TrieNode child represents a next possible letter. For example in the words edit and exit, the node for d would have a child i and a sibling x. x would have a child i.

public class TrieNode {

    private TrieNode sibling;
    private TrieNode child;
    private char key;
    ...
}

Upon the pressing of the tab key, the command box calls attemptAutoComplete, in the commandTrie class. With the content of the command box as the query, attemptAutoComplete searches for the query using the standard Trie search algortihm. If the query prefix itself is not present in the trie, then the attempt fails. If the query reaches a TrieNode with a sibling or child TrieNode, it will provide the user with possible commands for a given input. In this case, a dropdown box will appear in the UI that lists the possible commands for the user.

Upon finding a matching command, attemptAutoComplete returns it and replaces the text in the Command Line. If the no matching command is found, the text is turned red.

4.5.2. Optional Field Add Command [since v1.1]

MTM is designed to manage a multitude of players and the main function that would be used repetitively is the add command, hence it would alleviate the cumbersomeness of entering every detail of the players when you want the command to be executed quickly. Having the option to only entering the mandatory fields and leaving the optional ones empty will make the process of adding players more efficient.

Current Implementation

This mechanism enhances the original implementation of the add command. The required fields that are mandatory to be filled by the user are n/NAME and e/EMAIL. This implementation of making fields optional for AddCommand allows user to add players without their full information, and can be updated accordingly when the user retrieves their full information.

Code snippet from ParserUtil.java that shows the implementation of when a field is left empty. "<UNSPECIFIED>" string will be return when the value passed in is empty.

public class ParserUtil {
    public static final String UNSPECIFIED_FIELD = "<UNSPECIFIED>";
    //...ParserUtil code...

    public static Optional<String> parseValue(Optional<String> value, String messageConstraints)
        throws IllegalValueException {
    if (value.isPresent() && value.get().equals(UNSPECIFIED_FIELD)) {
        throw new IllegalValueException(messageConstraints);
    } else {
        return Optional.of(value.orElse(UNSPECIFIED_FIELD));
    }
    //...ParserUtil code ...
}

Code snippet from AddCommandParser.java that shows the usage of parsing fields that are set as optional.

public AddCommand parse(String args) throws ParseException {
        //...tokenize arguments...
        if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_EMAIL)
            || !argMultimap.getPreamble().isEmpty()) {
            throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
                AddCommand.MESSAGE_USAGE));
        }

        //...get person details from arguments...
        Address address = ParserUtil.parseAddress(ParserUtil.parseValue(argMultimap
            .getValue(PREFIX_ADDRESS), Address.MESSAGE_ADDRESS_CONSTRAINTS)).get();

        return new AddCommand(person);
        //...AddCommandParser code...
}

Code snippet from Address.java that shows an example of an optional field being valid when not specified.

public class Address {
    //...Address code...
    public static boolean isValidAddress(String test) {
        return test.matches(ADDRESS_VALIDATION_REGEX) || test.equals(UNSPECIFIED_FIELD);
    }
}
Design Consideration
Aspect: Implementation of Optional fields
  • Alternative 1 (current choice): Set a default value for unspecified fields and parse fields that are mandatory.

    • Pros: Easy to implement as fields are still filled with information even though user did not specify.

    • Cons: Unattractive display of fields when it is unspecified.

  • Alternative 2: Require users to input all fields.

    • Pros: The current implementation is used, hence there is no additional code to ensure validity of unspecified input.

    • Cons: Less flexibility is given to the user when adding a player into the application.

4.6. UI Enhancements

4.6.1. Team Display Bar [since v1.3]

Current Implementation

The Team Display Bar is implemented as TeamDisplay in the UI Component and renders TeamDisplay.fxml. It is called from MainWindow and highlights the current team that has been selected in the Command Line Interface by the user.

It calls the Team model and displays the Player cards associated with that Team. It contains event handler methods such as handleShowNewTeamEvent(), handleHighlightSelectedTeamEvent(), and handleDeselectTeamEvent(), which update the UI accordingly.

The component interactions can be seen in the high level sequence diagram for TeamDisplay below, using the example of a create command:

ShowNewTeamDiagram
Figure 35. Component interactions for create Team command

Code snippet from 'TeamDisplay' to show initialisation of UI component and event handlers:

public class TeamDisplay extends UiPart<Region> {

    private static final String FXML = "TeamDisplay.fxml";

    public TeamDisplay() {
        super(FXML);
        //...initialise Teams code...
        registerAsAnEventHandler(this);
    }

    @Subscribe
    private void handleShowNewTeamEvent(ShowNewTeamNameEvent event) {
        //...handleShowNewTeamEvent code...
    }

    @Subscribe
    private void handleHighlightSelectedTeamEvent(HighlightSelectedTeamEvent event) {
        //...handleHighlightSelectedTeamEvent code...
    }

    @Subscribe
    private void handleDeselectTeamEvent(DeselectTeamEvent event) {
        //...handleDeselectTeamEvent code...
    }
}
Design Considerations
Aspect: User Experience
  • Alternative 1 (current choice): TeamDisplay is placed underneath the ResultDisplay and above the PersonListPanel.

    • Pros: Located at an obvious location for the user to view the team currently selected.

    • Cons: There will be less space for the ResultDisplay and CommandBox.

  • Alternative 2: TeamDisplay is placed on the left of the PersonListPanel as a vertical bar.

    • Pros: There will be more space for the ResultDisplay and CommandBox.

    • Cons: It is less obvious to the user as it is at the side.

4.6.2. Player Details Pane [since v1.4]

Current Implementation

The Player Details pane is implemented as PlayerDetails in the UI Component. It is called from PlayerListPanel. It renders PlayerDetails.fxml and displays the selected PersonCard. It calls the Person model and displays the fields in the Person model that are not displayed in the left panel. It contains the event handler method handlePersonDetailsChangedEvent(), which updates the UI component when the edit ` or `remark commands are entered.

The component interactions can be seen in the high level sequence diagram for PlayerDetails below, using the example of a remark command:

PlayerDetailsDiagram
Figure 36. Component interactions for remark 1 r/test command

Code snippet from 'PlayerDetails' to show initialisation of UI component:

public class PlayerDetails extends UiPart<Region> {

    private static final String FXML = "PlayerDetails.fxml";
    public final Person person;

    public PlayerDetails(Person person) {
        super(FXML);
        this.person = person;
        //....player details code...
    }

    @Subscribe
    private void handlePersonDetailsChangedEvent(PersonDetailsChangedEvent event) {
        ...handlePersonDetailsChangedEvent code...
    }

}
Design Considerations
Aspect: User Experience
  • Alternative 1 (current choice): PlayerDetails is placed on the right of Person List Panel, replacing the BrowserPanel.

    • Pros: It is the only large unused space in the software left and is right beside the Person List Panel, thus is the logical place to look at after selection of a person card.

    • Cons: This is a lot of whitespace in the pane as are not many fields.

  • Alternative 2: PlayerDetails pane size is reduced and the extra space is repurposed.

    • Pros: There will be more space for another new feature e.g. calendar.

    • Cons: It can only be implemented in v2.0 due to time constraints in development.

4.6.3. Change Theme Command [since v1.5]

Current Implementation

The ChangeThemeCommand is a new feature that allows user to change the current theme to another theme. A new css class is implemented to accommodate the new theme, LightTheme. The MainWindow class is also changed to contain a handleChangeThemeRequestEvent() method which is an event handler to setAddressBookTheme, which is a method in UserPrefs.

Below is the sequence diagram for how the ChangeThemeCommand works:

ChangeThemeDiagram
Figure 37. Interactions of the Logic Component with the UI and Model Components for the changeTheme Command

Code snippet from 'ChangeThemeCommand':

public class ChangeThemeCommand extends Command {

    public ChangeThemeCommand(String theme) {
            this.theme = theme.trim();
    }

   @Override
    public CommandResult execute() throws CommandException {
        //...check for valid theme code...
        EventsCenter.getInstance().post(new ChangeThemeEvent(this.theme));
        return new CommandResult(String.format(MESSAGE_THEME_SUCCESS, this.theme));
    }
}
Design Considerations
Aspect: Command Syntax
  • Alternative 1 (current choice): The command syntax is in the form "changeTheme Dark" or "changeTheme Light".

    • Pros: This supports future implementation of more themes, so that the developer can easily add the new themes without having to change the execution.

    • Cons: The command is longer than it could be. (see alternative 2)

  • Alternative 2: The command syntax in the form "changeTheme", which would automatically toggle the theme.

    • Pros: User does not have to type anything to change the theme, so it might be more user friendly.

    • Cons: Future implementation of more themes would be harder for the developer as the toggle function would have to be changed quite drastically to become a command for selecting a theme out of multiple themes.

Aspect: User Experience
  • Alternative 1 (current choice): ChangeThemeCommand is implemented as a CLI command.

    • Pros: Consistent with the rest of the application, of which all changes are made by the CLI.

    • Cons: User has yet another command to remember the syntax of.

  • Alternative 2: Change of theme is implemented as a button to change onClick.

    • Pros: User does not have to type anything to change the theme, so it might be more user friendly.

    • Cons: Inconsistent with the rest of the application, which is CLI-based.

4.6.4. Set Tag Colour feature [since v1.1]

Current Implementation

The Set Command is an entirely new command that allows the user to assign a colour to a specific tag. This mechanism is facilitated by the SetCommandParser, which creates and returns a new SetCommand. In SetCommandParser, which implements the Parser interface, it parses the arguments inputted into the CLI, and checks whether the arguments are valid.

SetCommandParser is implemented as such:

public class SetCommandParser implements Parser<SetCommand> {

    public SetCommand parse(String args) throws ParseException {

    // ...parse arguments and check for invalid arguments...
  }
}

SetCommand inherits the abstract Command class. After execute() is called in SetCommand, the tag colour is set through the logic portions of ModelManager and AddressBook, then lastly changes tagColour attribute within the Tag object itself. It also posts an event in SetCommand, to which its handler in PersonCard responds and performs the UI update.

SetCommand is implemented in this way:

public class SetCommand extends Command {

    private final Tag tagToSet;
    private final String tagColour;

    public SetCommand(Tag tag, String colour) {
        requireNonNull(tag);
        tagToSet = tag;
        tagColour = colour;
    }

    @Override
    public CommandResult execute() {
    requireNonNull(model);
        boolean isTagValid = model.setTagColour(tagToSet, tagColour);
        //...check for valid tagName code....
        EventsCenter.getInstance().post(new ChangeTagColourEvent(tagToSet.getTagName(), tagColour));
        return new CommandResult(String.format(MESSAGE_SUCCESS, tagToSet.toString(), tagColour));
    }
}

The following sequence diagram shows how the set command operation works:

SetTagCommandDiagram
Figure 38. Interactions of the Logic Component with the UI and Model Components for the setTagColour Command
Design Considerations
Aspect: Implementation of Command vs UndoableCommand
  • Alternative 1 (current choice): Inherit from Command.

    • Pros: Does not involve complicated undo/redo tests, simple and quicker implementation, lessen chances of mistakes made in implementation.

    • Cons: User cannot use the undo/redo command.

  • Alternative 2 : Inherit from UndoableCommand.

    • Pros: User can utilise the undo/redo command.

    • Cons: Hard for developers to implement extra tests, not very necessary as users can just as easily type out the colour they would like to change their tag to; it is a short command, especially with the stc alias.

5. Documentation

We use asciidoc for writing documentation.

ℹ️
We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting.

5.1. Editing Documentation

See UsingGradle.adoc to learn how to render .adoc files locally to preview the end result of your edits. Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your .adoc files in real-time.

5.2. Publishing Documentation

See UsingTravis.adoc to learn how to deploy GitHub Pages using Travis.

5.3. Converting Documentation to PDF format

We use Google Chrome for converting documentation to PDF format, as Chrome’s PDF engine preserves hyperlinks used in webpages.

Here are the steps to convert the project documentation files to PDF format.

  1. Follow the instructions in UsingGradle.adoc to convert the AsciiDoc files in the docs/ directory to HTML format.

  2. Go to your generated HTML files in the build/docs folder, right click on them and select Open withGoogle Chrome.

  3. Within Chrome, click on the Print option in Chrome’s menu.

  4. Set the destination to Save as PDF, then click Save to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below.

chrome save as pdf
Figure 39. Saving documentation as PDF files in Chrome

6. Testing

If you are intending to develop our software further, it is highly recommended that you run tests in the ways listed below.

6.1. Running Tests

There are three ways to run tests.

💡
The most reliable way to run tests is the 3rd one. The first two methods might fail some GUI tests due to platform/resolution-specific idiosyncrasies.

Method 1: Using IntelliJ JUnit test runner

  • To run all tests, right-click on the src/test/java folder and choose Run 'All Tests'

  • To run a subset of tests, you can right-click on a test package, test class, or a test and choose Run 'ABC'

Method 2: Using Gradle

  • Open a console and run the command gradlew clean allTests (Mac/Linux: ./gradlew clean allTests)

ℹ️
See UsingGradle.adoc for more info on how to run tests using Gradle.

Method 3: Using Gradle (headless)

Thanks to the TestFX library we use, our GUI tests can be run in the headless mode. In the headless mode, GUI tests do not show up on the screen. That means the developer can do other things on the Computer while the tests are running.

To run tests in headless mode, open a console and run the command gradlew clean headless allTests (Mac/Linux: ./gradlew clean headless allTests)

6.2. Types of tests

We have two types of tests:

  1. GUI Tests - These are tests involving the GUI. They include,

    1. System Tests that test the entire App by simulating user actions on the GUI. These are in the systemtests package.

    2. Unit tests that test the individual components. These are in seedu.address.ui package.

  2. Non-GUI Tests - These are tests not involving the GUI. They include,

    1. Unit tests targeting the lowest level methods/classes.
      e.g. seedu.address.commons.StringUtilTest

    2. Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
      e.g. seedu.address.storage.StorageManagerTest

    3. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
      e.g. seedu.address.logic.LogicManagerTest

6.3. Troubleshooting Testing

Problem: HelpWindowTest fails with a NullPointerException.

  • Reason: One of its dependencies, UserGuide.html in src/main/resources/docs is missing.

  • Solution: Execute Gradle task processResources.

7. Dev Ops

This section covers resources for you to develop this software with good practices and prepare it for release.

7.1. Build Automation

See UsingGradle.adoc to learn how to use Gradle for build automation.

7.2. Continuous Integration

We use Travis CI and AppVeyor to perform Continuous Integration on our projects. See UsingTravis.adoc and UsingAppVeyor.adoc for more details.

7.3. Coverage Reporting

We use Coveralls to track the code coverage of our projects. See UsingCoveralls.adoc for more details.

7.4. Documentation Previews

When a pull request has changes to asciidoc files, you can use Netlify to see a preview of how the HTML version of those asciidoc files will look like when the pull request is merged. See UsingNetlify.adoc for more details.

7.5. Making a Release

Here are the steps to create a new release.

  1. Update the version number in MainApp.java.

  2. Generate a JAR file using Gradle.

  3. Tag the repo with the version number. e.g. v0.1

  4. Create a new release using GitHub and upload the JAR file you created.

7.6. Managing Dependencies

A project often depends on third-party libraries. For example, Address Book depends on the Jackson library for XML parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives.
a. Include those libraries in the repo (this bloats the repo size)
b. Require developers to download those libraries manually (this creates extra work for developers)

Appendix A: Product Scope

This section tells you more details of our software, our target users, the user stories, and gives you a sneak peak into our development process.

A.1. Target user profile:

  • football team managers

  • has a need to manage a significant number of contacts

  • prefer desktop apps over other types

  • can type fast

  • prefers typing over mouse input

  • is reasonably comfortable using CLI apps

A.2. Value proposition:

Exclusive application for management of footballers and football teams that provides an enhanced listing of footballers and convenient lookup on updated information of players.

A.3. Feature Contribution

  • Codee

    • Major - Revamp MTM’s whole GUI for ease of viewing teams and players’ details.

      • e.g. remove browser panel, add player details panel, change person card.

    • Minor - Command to set the colour of tags to colour of choice.

  • Jordan

    • Major - Implementation of Team Component and Related Functions.

      • e.g. creation of team, assignment of players, viewing of teams, removal of team, renaming of team.

    • Minor

      • Introduce remark command and remark field.

      • Enhanced add functionality to allow optional fields.

  • Syafiq

    • Major - Create a new player class that contains more information about the players.

      • e.g. Position, Rating (0 - 5 Star), Remarks

      • Update add, edit ,list, sort to include these fields accordingly.

    • Minor - Autocomplete command

  • Tianwei

    • Major - privacy

      • Set private field and passwords

      • Make accounts

    • Minor - Sorting players by different fields

Appendix B: User Stories

Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *

Priority As a …​ I want to …​ So that I can…​

First Time User Stories

* * *

new user

see usage instructions

refer to instructions when I forget how to use the App

* * *

new user

access the user guide

find out how to use fancy features of the application

* * *

new user

see a list of instructions available

navigate and use the application easily

Player Related User Stories

* * *

team manager

add a new player

* * *

team manager

add player’s address

mail him important documents

* * *

team manager

add player’s contact number

contact him when needed

* * *

team manager

add player’s email

email him when needed

* * *

team manager

add player’s jersey number

easily identify them during the game

* * *

team manager

add player’s position

easily pick my lineup for match

* * *

team manager

add player’s ratings

easily identify the better players

* * *

team manager

delete a player

remove him if I kick him out from the team or he quits

* * *

team manager

find a player by name

locate details of players without having to go through the entire list

* * *

team manager

edit player’s contact number

* * *

team manager

edit player’s email

* * *

team manager

edit player’s jersey number

* *

team manager

add player’s match stats (e.g. goals scored)

decide the lineup, give award

* *

team manager

add player remarks

for self note

* *

team manager

add player’s avatar

for facial recognition

* *

team manager

edit player’s position

easily pick my lineup for match

* *

team manager

edit player’s address

* *

team manager

edit player’s avatar

* *

team manager

edit player remarks

for self note

* *

team manager

hide private contact details by default

minimize chance of someone else seeing them by accident

*

team manager

edit player’s name

*

team manager with many players in the team managing application

sort by player name

locate a person easily

Team Related User Stories

* * *

team manager

create teams

organize and manage my players through their respective team

* * *

team manager

assign player to teams

identify the team that the player is playing for

* * *

team manager

view players in specified team

identify the lineup of the team and which player belongs to which team

* * *

team manager

remove teams

remove teams that I no longer managed

* *

team manager

set match with competing team

acknowledge and plan training session for the team

* *

team manager

view upcoming match

keep track of the upcoming matches with opponent teams

* *

team manager

edit player’s allocated team

move players between teams

* *

team manager

schedule training programs for team

I do not have any conflicting schedule between different teams

* *

team manager

send reminder to team of schedule

my players does not forget about training session

*

team manager

sort by team name

identify group lineup easily

Additional User Stories

* *

team manager who remembers better with visual

set colours to tags

easily identify the tag that I have set to players

* *

team manager

autofill command

perform task quickly

* *

team manager

password login

if team manager wants to protect certain information

* *

team manager

submit feedback to developers

developers can improve the application constantly

Appendix C: Use Cases

(For all use cases below, the System is the MTM and the Team Manager is the user, unless specified otherwise)

Use case: Add player

MSS

  1. User enter command to add

  2. MTM prompt user of format to enter player’s name and details

  3. User enter player’s name and details in required format

  4. User press enter to insert person into storage

    Use case ends.

Extensions

  • 4a. User did not enter any details.

    • 4a1. MTM inform user that it is an invalid add command.

      Use case ends.

Use case: Edit player’s team

MSS

  1. User enter command to find

  2. User enter player’s name

  3. MTM display list of players found

  4. User enter command to edit player’s team using index of displayed list

    Use case ends.

Extensions

  • 2a. Player name does not exist.

    • 2a1. MTM inform user that player does not exist.

      Use case ends.

Use case: Find player by team name

MSS

  1. User enter command to find team

  2. User enter team name

  3. MTM display list of players in team

    Use case ends.

Extensions

  • 2a. Team name does not exist.

    • 2a1. MTM informs user that team name does not exist

      Use case ends.

Use case: Remove player from team

MSS

  1. User enter command to find

  2. User enter player’s name

  3. MTM display list of players found

  4. User enter remove command and index associated with player

  5. MTM displays player that is removed and updated list of players

    Use case ends.

Extensions

  • 4a. Invalid index entered.

    • 4a1. MTM informs user that index is invalid.

    • 4a2. User enters valid index.

      Use case resumes at step 5.

  • 4b. User removes wrong index unintentionally.

    • 4b1. User undo remove by entering command to undo.

      Use case ends.

{More to be added}

Appendix D: Non Functional Requirements

  1. Should work on any mainstream OS as long as it has Java 1.8.0_60 or higher installed.

  2. Should be able to hold up to 150 players without a noticeable sluggishness in performance for typical usage.

  3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.

  4. Works on both 32-bit and 64-bit machines

  5. Should respond within 1 second of query

  6. Should be intuitive and easy to use for a first-time user.

  7. Should be able to handle any sort of input, i.e. should recover from invalid input.

  8. Should have audience-focused user guides and developer guides.

  9. Should have command names that concisely describe their function.

  10. Should be an open-source project.

  11. Development be cost effective or free.

  12. App should be able to work offline.

  13. Should save and backup the state of the team managing application regularly.

  14. Current versions must be backward compatible with older versions to support undo.

  15. The user interface should be simple and minimise distractions so that user can continue with their work in a focused manner.

Appendix E: Glossary

Mainstream OS

Windows, Linux, Unix, OS-X

Private contact detail

A contact detail that is not meant to be shared with others

Lineup

A list of players that are playing for a match, with their positions specified

User Guide

A documentation on the functionality and usability of MTM

Appendix F: Instructions for Manual Testing

Given below are instructions to test the app manually.

ℹ️
These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.

F.1. Launch and Shutdown

  1. Initial launch

    1. Download the jar file and copy into an empty folder

    2. Double-click the jar file
      Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.

  2. Saving window preferences

    1. Resize the window to an optimum size. Move the window to a different location. Close the window.

    2. Re-launch the app by double-clicking the jar file.
      Expected: The most recent window size and location is retained.

{ more test cases …​ }

F.2. Deleting a player

  1. Deleting a player while all players are listed

    1. Prerequisites: List all players using the list command. Multiple players in the list.

    2. Test case: delete 1
      Expected: First player is deleted from the list. Details of the deleted player shown in the status message. Timestamp in the status bar is updated.

    3. Test case: delete 0
      Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.

    4. Other incorrect delete commands to try: delete, delete x (where x is larger than the list size) {give more}
      Expected: Similar to previous.

{ more test cases …​ }

F.3. Saving data

  1. Dealing with missing/corrupted data files

    1. {explain how to simulate a missing/corrupted file and the expected behavior}

{ more test cases …​ }

F.4. Remarking a player

  1. Remarking a player while all players are listed

    1. Prerequisites: List all players using the list command. Multiple players in the list.

    2. Test case: remark 1 r/This is my remark to you.
      Expected: First player is remarked from the list. Details of the remarked player shown in the status message. Timestamp in the status bar is updated.

    3. Test case: remark 0
      Expected: No person is remarked. Error details shown in the status message. Status bar remains the same.

    4. Other incorrect delete commands to try: remark, remark x (where x is larger than the list size), remark -1, remark 2 x/ (where x is not the prefix for remark)
      Expected: Similar to previous.

    5. Test case: remark 1
      Expected: First player has its remark removed. Details of the remarked player shown in the status message. Timestamp in the status bar is updated.

    6. Test case: remark 1 r/I want to delete this.remark 1 r/
      Expected: Similar to previous.

F.5. Adding a player without all fields

  1. Adding a player where player is unique

    1. Prerequisites: Newly added player must not exist in MTM.

    2. Test case: add n/Tumeric Turner e/[email protected]
      Expected: Player added into MTM and will be on the list. Details of the newly added player shown in the status message. Fields that are not input during addition are replaced with <UNSPECIFIED> Timestamp in the status bar is updated.

    3. Test case: add n/Guavi Hollae
      Expected: Player is not added into MTM. Errors details shown in status message. Name and email are compulsory fields that need to be entered for add to work.

F.6.1. Creating a Team

  1. Creating a Team with current existing data in MTM

    1. Prerequisites: Team currently does not exist in MTM.

    2. Test case: create NUSTeam
      Expected: Team is created and displayed in Team Display Panel. Team name will be shown in the status message. Timestamp in the status bar is updated.

    3. Test case: create NUSTeam
      Expected: Team is not created due to team already existing in MTM. Team Display Panel remains the same. Error details shown in the status message. Status bar remains the same.

    4. Test case: create &-Team-&
      Expected: Team does not allow names with special characters, only alphanumeric, can consist of all numbers and can contain space in name. Team Display Panel remains the same. Error details shown in the status message. Status bar remains the same.

F.6.2. Viewing a Team

  1. Viewing a Team with current existing data in MTM

    1. Prerequisites: Team currently exist in MTM.

      1. Test case: view Arsenal
        Expected: Team in Team Display Panel will be selected. Player list will be updated with list of players that are in the Team. Team name will be shown in the status message.

      2. Test case: view NonExistingTeam
        Expected: Team is not viewed as it does not exist in MTM. Error details shown in the status message.

      3. Test case: view chelsea
        Expected: Team is not viewed as it does not exist in MTM, as team names are case sensitive. Error details shown in the status message.

    2. Prerequisites: The command create NUSTeam was executed right before executing this test case.

      1. Test case: view NUSTeam
        Expected: Team in Team Display Panel will be selected. Player list will be empty as no players are in the team yet. Team name will be shown in the status message.

F.6.3. Assigning Players to Team

  1. Assigning players to a Team after executing create NUSTeam

    1. Test case: listassign NUSTeam i/1 3 5
      Expected: Team in Team Display panel will be selected. Player list will be updated with list of players that are in the Team with the newly assigned players. Each individual player assign will be shown in the status message. Timestamp in the status bar is updated.

    2. Test case: listassign NUSTeam i/3
      Expected: No player is assigned as player already exist in the team. Error details shown in the status message. Status bar remains the same.

    3. Test case: listassign NUSTeam i/2 3 4
      Expected: Only the player at index 2 is assigned. Assign command will stop once it detects that a player cannot be assigned. Team Display Panel will not be updated. Person Card of index 2 will be updated with assigned team. Timestamp in the status bar is updated.

    4. Test case: listassign NUSTeam i/9 6 4
      Expected: All players will be assigned by ascending index order. Team in Team Display panel will be selected. Player list will be updated with list of players that are in the Team with the newly assigned players. Each individual player assign will be shown in the status message. Timestamp in the status bar is updated.

    5. Test case: listassign NUSTeam i/7 40
      Expected: Index exceeding the number of players listed in the Player List Panel will be ignored. Team in Team Display panel will be selected. Player list will be updated with list of players that are in the Team with the newly assigned players. Each individual player assign will be shown in the status message. Timestamp in the status bar is updated.

    6. Test case: listassign NUSTeam i/-1
      Expected: Invalid index will have error details shown in the status message. Team is not viewed.

    7. Test case: listassign NUSTeam
      Expected: Error details shown in the status message. Invalid command format as no index is given. Team is not viewed.

    8. Test case: assign NoTeam
      Expected: Error deatils shown in the status message. No such team found in MTM. Team is not viewed, player list not updated.

    9. Test case: add n/Barry Putter e/[email protected] tm/NUSTeam
      Expected: New person is added into MTM. Team in Team Display Panel will be selected. Player list will be updated with the new added person. Timestamp in the status bar is updated.

    10. Test case: add n/Himonie Branger e/[email protected] tm/MissingTeam
      Expected: Person is not added into MTM. Team does not exist in MTM. Error details shown in the status message.

  2. Unassigning players from a Team

    1. Prerequisites: Players are already existing in the team

    2. Test case: view NUSTeamassign i/1 2 3
      Expected: Player list will be updated as the specified players to be unassigned will be removed from the team. Each individual player unassign will be shown in the status message. Timestamp in the status bar is updated.

    3. Test case: listassign i/10 11
      Expected: Player list will be updated with the specified players to be unassigned will have their team name updated to <UNSPECIFIED>. Each individual player unassign will be shown in the status message. Timestamp in the status bar is updated.

    4. Test case: assign i/NUS
      Expected: Invalid index will have error details shown in the status message.

    5. Test case: listassign i/1
      Expected: Error details shown in the status message. Player already has no team and can’t be unassigned.

F.6.4. Renaming a Team

  1. Renaming a Team after assigning players above

    1. Prerequisites: Ensure that the team to be renamed into does not exist

    2. Test case: rename NUSTeam tm/SUNTeam
      Expected: Team in Team Display Panel will be renamed to the new team name. New team name will be selected in the Team Display Panel. Player list of all the players in the new team name will be displayed. All players in the team will have their team name updated to the new team name. Team rename will be shown in the result status message. Timestamp in the status bar is updated.

    3. Test case: rename NewTeam tm/MooTeam
      Expected: Error details shown in the status message. Team to be rename does not exist in MTM.

    4. Test case: rename SUNTeam tm/&-Team
      Expected: Error details shown in the status message. Team to be rename into is invalid and should only contains alphanumeric characters.

    5. Test case: rename tm/SmoovTeam
      Expected: Error details shown in the status message. Did not specify which team to rename and will indicate invalid command format.

F.6.5. Removing a Team

  1. Removing a Team after renaming the team above

    1. Prerequisites: Team currently exist in MTM.

    2. Test case: view SUNTeamremove SUNTeam
      Expected: Team is removed and Team Display Panel is updated to reflect that the team has been removed. Team name that is being removed will be shown in the status message. All players in the team will have their team name updated in the Player List Panel. Player List Panel will show the full list of players in MTM. Timestamp in the status bar is updated.

    3. Test case: listremove Arsenal
      Expected: Similar to above, will be displaying full list of players before and after remove command.

    4. Test case: remove SUNTeam
      Expected: Error details shown in the status message. Team does not exist in MTM.

F.7.1. Changing a Theme

  1. Changing the theme that MTM is currently on

    1. Test case: cte Dark (if current theme is Light) or cte Light (if current theme is Dark)
      Expected: MTM colour scheme will change to the respective themes as shown below:

DarkTheme
LightTheme

F.7.2. Setting Tag Colours

  1. Setting the tags to colour of choice

    1. Prerequisites: Player has tags

    2. Test case: setTagColour t/redCard tc/blue Expected: All tags with the name redCard would become blue