diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index aabbbdbaf33..81267b247eb 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -43,4 +43,4 @@ jobs: with: directory: ${{ github.workspace }}/build/reports/jacoco/coverage files: coverage.xml - fail_ci_if_error: true + fail_ci_if_error: false diff --git a/README.md b/README.md index 13f5c77403f..7bc2badc347 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,28 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +# Minefriends 👾 -![Ui](docs/images/Ui.png) +[![CI Status](https://github.com/AY2223S1-CS2103T-T10-4/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2223S1-CS2103T-T10-4/tp/actions) + +This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). + +:bulb: Before we begin, it is important to note that: +* Minefriends is not affiliated with Minecraft, Mojang Studios or Microsoft in any way. +* Minefriends is an independently developed software. It is not a product owned by Mojang Studios or Microsoft. +* Team Minefriends do not own Minecraft, in whole or in part. + +Minefriends is a **desktop app for avid Minecraft players to manage information about their online friends**. + +With Minefriends, you can: + +- Maintain a list of all your online friends +- Access your friends' information easily, such as their emails and social handles +- Keep track of which servers and timezones your friends are playing on -* This is **a sample project for Software Engineering (SE) students**.
- Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. - * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. - * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. + +### Note: +Minefriends is optimized for use via a Command Line Interface (CLI), while also having the benefits of a Graphical User Interface (GUI). + + +**Download Minefriends and run it now! (- link or jar file to be added -)** + +Sample interface: +![Ui](docs/images/Ui.png) diff --git a/build.gradle b/build.gradle index 108397716bd..fa131a4f54a 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,7 @@ plugins { id 'com.github.johnrengelman.shadow' version '7.1.2' id 'application' id 'jacoco' + id 'org.openjfx.javafxplugin' version '0.0.8' } mainClassName = 'seedu.address.Main' @@ -56,13 +57,27 @@ dependencies { implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-media', version: javaFxVersion, classifier: 'linux' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' + // https://mvnrepository.com/artifact/org.controlsfx/controlsfx + implementation group: 'org.controlsfx', name: 'controlsfx', version: '11.0.3' + + implementation group: 'org.testfx', name: 'testfx-core', version: '4.0.16-alpha' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: jUnitVersion testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion + + testRuntimeOnly group: 'org.testfx', name: 'openjfx-monocle', version: 'jdk-12.0.1+2' +} + +run { + enableAssertions = true } shadowJar { diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..c87972b261b 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -5,55 +5,61 @@ title: About Us We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg). -You can reach us at the email `seer[at]comp.nus.edu.sg` +You can reach us at the email `zbz.lvlv@gmail.com` ## Project team -### John Doe +### Zhang Bozheng - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/zbz-lvlv)] -* Role: Project Advisor +[[portfolio](team/zbz-lvlv.md)] -### Jane Doe +* Role: Team Lead +* Responsibilities: All - +### Kang Wenhan -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] + -* Role: Team Lead -* Responsibilities: UI +[[github](http://github.com/onepersonhere)] + +[[portfolio](team/onepersonhere.md)] -### Johnny Doe +* Role: Co-Lead +* Responsibilities: Code Quality - +### Justin Cheng -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] + + +[[github](http://github.com/Chustinjeng)] + +[[portfolio](team/chustinjeng.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: Functional developer and test cases -### Jean Doe +### Shawn Tan - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/shawnkai)] + +[[portfolio](team/shawnkai.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: Code Quality + +### Sheryl Kong -### James Doe + - +[[github](http://github.com/sherylkong18)] -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[portfolio](team/sherylkong18.md)] * Role: Developer * Responsibilities: UI diff --git a/docs/Configuration.md b/docs/Configuration.md index 13cf0faea16..674a2c8e1e3 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -1,6 +1,3 @@ ---- -layout: page -title: Configuration guide ---- +# Configuration guide Certain properties of the application can be controlled (e.g user preferences file location, logging level) through the configuration file (default: `config.json`). diff --git a/docs/DevOps.md b/docs/DevOps.md index 26354050fa4..244fd721ce2 100644 --- a/docs/DevOps.md +++ b/docs/DevOps.md @@ -1,7 +1,4 @@ ---- -layout: page -title: DevOps guide ---- +# DevOps guide * Table of Contents {:toc} diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 46eae8ee565..fad2947083b 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,7 +1,58 @@ --- layout: page -title: Developer Guide +title: 👾 Minefriends Developer Guide --- + +
+ +:bulb: Before we begin, it is important to note that: +* Minefriends is not affiliated with Minecraft, Mojang Studios or Microsoft in any way. +* Minefriends is an independently developed software. It is not a product owned by Mojang Studios or Microsoft. +* Team Minefriends do not own Minecraft, in whole or in part. + +
+ +### What is Minefriends + +Minefriends is an address book for Minecraft players to find friends to play Minecraft multiplayer with, +at the right time, with the right game modes and on the right servers. + +### What is this guide about + +This developer guide provides readers with the understanding of the motivations for the development of +Minefriends together with its technical design and implementation details. A broad view of the architecture of +Minefriends is given and selected implementation details of important features are given as well. + +### Who is this guide for + +This guide is written for +* Maintainers who wish to extend/alter the features of Minefriends +* Developers who wish to morph Minefriends for their own use +* Minecraft players who wish to adapt Minefriends to better suit their own needs + +### How to use this guide + +#### Organization + +The guide is organized in a way that it starts with the motivations of building Minefriends, followed +by a big-picture design overview of the various components before diving into individual feature implementations. +* To get a good general understanding of Minefriends, it is recommended to read the guide from top to bottom. +* However, you can also jump to a certain part using the [table of contents](#table-of-contents). + +#### Legend + +* Text in [blue](#how-to-use-this-guide) are links that you can use to navigate to another part of the document or to an external document/website. +* Text in **bold** are used to emphasize an important point. +* Text in `this` are used to refer to code or names related to the codebase. + +
+:bulb: Text in a blue box are tips; they provide additional information about a particular point. +
+ +-------------------------------------------------------------------------------------------------------------------- + +## **Table of contents** + * Table of Contents {:toc} @@ -9,17 +60,62 @@ title: Developer Guide ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +### External libraries +* [ControlsFX](https://github.com/controlsfx/controlsfx) +* [TestFX](https://github.com/TestFX/TestFX) + +### Graphics and assets +* App Icon - [Minecraft Book & Quill icon](https://minecraft.fandom.com/wiki/Book_and_Quill) +* Font - [Minecraft font](https://fontmeme.com/fonts/minecraft-font/) +* Background textures - [Dirt block](https://minecraft.fandom.com/wiki/Dirt) +* Background textures - [Grass block](https://minecraft.fandom.com/wiki/Grass_Block) +* Background textures - [Bedrock](https://minecraft.fandom.com/wiki/Bedrock) +* [Creeper texture](https://minecraft.fandom.com/wiki/Creeper?file=Charged_Creeper_JE1_BE1.png) +* [Creeper explosion texture](https://toppng.com/show_download/244692/explosion-pixel-art/large) +* Creeper explosion audio: Self-recorded in game + +These assets are copyright Mojang Studios, which are available for non-commercial use. +The terms of use can be found [here](https://www.minecraft.net/en-us/terms). -------------------------------------------------------------------------------------------------------------------- ## **Setting up, getting started** -Refer to the guide [_Setting up and getting started_](SettingUp.md). +Refer to the guide [Setting up and getting started](SettingUp.md). + +-------------------------------------------------------------------------------------------------------------------- + +## **Conceptualization of Minefriends** + +#### Target user persona + +A male 14 year old teenager who plays Minecraft multiplayer with his friends. +He is an expert Minecraft player. He has school and extracurricular commitments +but always finds time to play Minecraft. He plays a variety of multiplayer game modes +(eg. creative, survival games, skyblock, KitPvP etc.) and he has different friends who play +Minecraft with him in different ways. He is familiar with Minecraft commands so he +is comfortable with the command line interface (CLI). + +#### Target user profile + +* plays Minecraft multiplayer with friends regularly +* has many Minecraft friends (online and offline) from all over the world +* prefers desktop apps over other types +* prefers typing to mouse interactions and types fast +* is comfortable using the Minecraft command line, and by hence extension, using CLI apps + +#### Value proposition + +There are many servers and multiplayer game modes in Minecraft, and players have different schedules to when they can play. +We want to help players find the right people to play the right game mode with at the right time. + +#### User stories and use cases + +The user stories can be found in [Appendix A](#appendix-a-user-stories) and use cases in [Appendix B](#appendix-b-use-cases). -------------------------------------------------------------------------------------------------------------------- -## **Design** +## **Design of Minefriends**
@@ -75,7 +171,7 @@ The **API** of this component is specified in [`Ui.java`](https://github.com/se- The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. -The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml) +The `UI` component uses the JavaFx UI framework. The layout of these UI parts is defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml) The `UI` component, @@ -116,7 +212,7 @@ How the parsing works: ### Model component **API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) - + The `Model` component, @@ -150,93 +246,202 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa -------------------------------------------------------------------------------------------------------------------- -## **Implementation** +## **Selected implementation details** This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +### *Edit a Friend* -#### Proposed Implementation +The `edit` feature allows users to edit details of their friends. -The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations: +#### Implementation -* `VersionedAddressBook#commit()` — Saves the current address book state in its history. -* `VersionedAddressBook#undo()` — Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history. +The edit feature is facilitated through the `EditCommand` and `EditCommandParser` classes. First, the friend to +be edited is identified through the index of the person in the list of friends. Then, in +the `EditCommand` class, there is a `EditPersonDescriptor` nested class which takes in all +the updated details of the friend to be edited. For the fields that are not updated, the +original fields will be used in the `EditPersonDescriptor` class. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +Minefriends will then call the `createEditedPerson` method which will create a new person +with new details. This will then invoke a call to the `ModelManager` class to replace the target person +with the edited person in the addressBook. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +The following class diagram shows the organization of the classes for `edit`. -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. + -![UndoRedoState0](images/UndoRedoState0.png) +The following sequence diagram shows the flow of the execution of the +edit command. Some details related to the general parsing and execution of +commands are omitted as they have been explained under [logic](#logic-component). -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. + -![UndoRedoState1](images/UndoRedoState1.png) +### *Suggest a Friend* -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +The `suggest` feature suggests a friend for the user to play Minecraft with based on a given +set of constraints. A detailed description of its usage can be found [here](https://ay2223s1-cs2103t-t10-4.github.io/tp/UserGuide.html#suggest-me-a-friend-suggest) in the user guide. -![UndoRedoState2](images/UndoRedoState2.png) +#### Implementation -
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +The class diagram below shows the organization of the classes of the `suggest` feature, with explanation provided after. -
+ -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +The `suggest` feature is facilitated through the `SuggestCommand`, `SuggestCommandParser` +and `PersonSuggestionPredicate` classes. Once parsed, every `SuggestCommand` will contain +a `PersonSuggestionPredicate` such that the predicate can be used to filter +through the list of all friends to get a list of suggested friends. -![UndoRedoState3](images/UndoRedoState3.png) +The `PersonSuggestionPredicate` is made up of two parts, a collection of `DayTimeInWeek` and a collection of `Keyword`. +Minefriends will find all friends that are available for **any** of the `DayTimeInWeek` and contains **all** the keywords in +the collection of `Keyword`. -
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather -than attempting to perform the undo. +The following sequence diagram shows the flow of the execution of the suggest command. +Some details related to the general parsing and execution of commands are omitted +as they have been explained under [logic](#logic-component). -
+ -The following sequence diagram shows how the undo operation works: +The `filteredPersons` in the instance of `Model` will be filtered according to the `PersonSuggestionPredicate` +to contain only the friends who satisfy the predicate. More information about `Model` is available [here](#model-component). -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +#### Rationale for this implementation -
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +Using predicates allows us to decouple the `SuggestCommand` and `Model` instances, by containing the logic +for deciding who to filter in a separate class `PersonSuggestionPredicate`. -
+In addition, the use of predicate allows us to exploit the power of Java streams, which makes it easier +to write simpler and cleaner code. -The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +#### Alternatives considered -
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. +The `Model` instance can be passed directly to the `SuggestCommand` in which the `SuggestCommand` can +modify the `filteredPersons` list directly. However, this leads to tighter coupling which reduces the +maintainability of the code. -
+### *Autocomplete Commands* + +The autocomplete feature matches the current text to all the commands for the user when the user types in the command box. -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +#### Implementation -![UndoRedoState4](images/UndoRedoState4.png) +The autocomplete feature is facilitated through the `TextFields` class under the ControlsFX library. +The `TextFields` class provides a static method `bindAutoCompletion` that will create a new autocompletion binding between +the given TextField using the given autocomplete suggestions. -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. +Everytime the user modifies the input, a `AutoCompletePopup` object, which is a `PopupWindow`, will appear below the `CommandBox`. +The object will display a list of suggestions that matches the current text in the text field. -![UndoRedoState5](images/UndoRedoState5.png) +Alternative implementations of coming up with our own classes were considered aside from using the ControlsFX library. +However, coming up with the solution requires a great amount of effort for the same amount of functionality. +Hence, the decision was made to use the ControlsFX library. -The following activity diagram summarizes what happens when a user executes a new command: +The following activity diagram shows the workflow for the autocomplete feature. - + -#### Design considerations: +### *Available timings* -**Aspect: How undo & redo executes:** +The user can save the available timings of a friend to their profile, as a time interval during the week. -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +#### Implementation -* **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. +The available timings of a friend are stored as `TimeInterval` attributes in the `Person` class. +Each `TimeInterval` object will have two `DayTimeInWeek` fields, representing its start time and its end time. -_{more aspects and alternatives to be added}_ +The time stamp that is parsed into the `DayTimeInWeek` class is then saved as the number of elapsed minutes since Monday 12am. -### \[Proposed\] Data archiving +#### Additional details -_{Explain here how the data archiving feature will be implemented}_ +To support our `Suggest` command in finding friends who are available at a certain time, the `TimeInterval` class implements the `isAvailable()` method in the `ITimesAvailable` interface. +The `isAvailable()` method takes in a time stamp as a `DayTimeInWeek` object, and calculates if it lies within the available time interval of the friend. + +The following class diagram shows the relationship between the classes: + + + +Furthermore, in order to use the `Suggest` command to find friends who are playing at the +current moment, the `SuggestCommandParser` invokes the `parseDayTimeInWeek` method of the +`ParserUtil` class to convert the current time to a `DayTimeAndWeek` object. Inbuilt Java APIs +such as `LocalDateTime` and `DayOfWeek` are used to get the current time. + +The following sequence diagram explains how the current time is parsed into +a `DayTimeInWeek` object, starting from the `parseDayTimeInWeek` method +of `ParserUtil`. Following which, you may refer to the [Suggest](#suggest-a-friend) section which +explains in greater detail how the `Suggest` command functions to find friends who are available at +certain timings. + + + +### *Servers, GameTypes and Socials as Coloured Tags in Friend's profile* +Whenever the user updates the in-game preferences or social handles of a friend, they are displayed as +distinct coloured tags under the friend's profile. + +#### Implementation + +The display feature is facilitated through the `PersonCard`, `PersonListPanel` and `UIPart` classes, +and configured using the `DarkTheme.css` file. + +The `PersonCard` class inherits from the `UIPart` class, representing the panel that displays each friend's profile. +Inside the `PersonCard` class, there are multiple `FlowPane` fields, each representing a type of user preference. + +Everytime the user updates a friend's information, a new `PersonCard` object is created. The `PersonCard` object +will retrieve the corresponding friend's information using the `toDisplayString()` method in the `Person` class, and add +their in-game preferences and social handles as labelled tags under the friend's profile. + +Each type of user preference is then tagged with a different colour, specified in the `DarkTheme.css` file, for +easy differentiation of information. + +The following class diagram shows the relationship between the classes in the UI system: + + + +#### Design Consideration + +#### Which user information should be shown as tags? + +* **Alternative 1 (current choice):** Only in-game preferences (such as preferred game types and Minecraft servers) and social handles. + * Pros: + * Strategic display of only the more important user details. + * Less cluttered, cleaner user interface. + * Cons: + * We have to decide which user details are of higher priority to Minefriends users, which may differ from user to user. +* Alternative 2: Show all information as tags. + * Pros: + * Easy to implement as the format is consistent for all information. + * Cons: + * Too many tags displayed can make it harder to users to find the information they need in one glance. + +### User-Friendly Representation of Various Servers + +The representation of various Minecraft servers is in the format +of `server name@server address`. Each server can have duplicate names but each +server will have a unique server address. + +#### Reason for Implementation +The previous representation of Minecraft servers was in the format of solely +a server address e.g. `111.111.111.111` + +This is much less user-friendly as compared to the new representation +where users are able to remember various servers by their server names, +and distinguish servers with the same names by their server addresses +e.g. `Mineplex@111.111.111.111` + +#### Implementation + +The server class currently only allows the server to be documented in the +format of an IP address. + +With the input `ms/111.111.111.111`,
+1) The `Parse` method in `AddCommandParser` will recognize the `prefix_minecraft_server`.
+2) The method will then call `parseServers` method of `ParserUtil`.
+3) `parseServers` method of `ParserUtil` will examine the validity of the + server name by calling the `parseServer` method of `ParserUtil`.
+4) If it is a valid server name, a new server class will be created and + added into the set of Server.
+5) The set of Server will then be an attribute of the new `Person` created. + +![ServerRepresentationSequenceDiagram](images/ServerRepresentationSequenceDiagram.png) -------------------------------------------------------------------------------------------------------------------- @@ -251,48 +456,99 @@ _{Explain here how the data archiving feature will be implemented}_ -------------------------------------------------------------------------------------------------------------------- -## **Appendix: Requirements** +## **Appendix A: User stories** + +Priority legend +* High: Must have +* Medium: Good to have +* Low: Unlikely to have + +| Priority | As a | I want to | so that I can | +|---------|------------------|----------------------------------------------------|-----------------------------------------------------------------------------| +| High | new user | see usage instructions | learn how to use Minefriends | +| High | Minecraft player | add my friends to Minefriends | remember information about them | +| High | Minefraft player | remove a friend from Minefriends | keep an accurate list of my Minecraft friends | +| High | Minecraft player | view all my friends | have an overview of my Minecraft social contacts | +| High | Minecraft player | save all my friends' information | retrieve them on subsequent uses of Minefriends | +| High | Minecraft player | receive suggestions on which friends to play with | play Minecraft with the right friend at the right time | +| High | Minecraft player | know my friend's name | address them correctly | +| High | Minecraft player | know my friend's Minecraft username | recognize them on Minecraft servers | +| High | Minecraft player | know my friend's phone number | call/text them when I want to play with them | +| High | Minecraft player | know my friend's social media handles | contact them when I want to play with them | +| High | Minecraft player | know my friend's preferred game modes | find friends with compatible game type interests | +| High | Minecraft player | know my friend's preferred Minecraft servers | find friends with compatible server interests with me | +| High | Minecraft player | know my friend's preferred play timings | find friends who are free at a particular time | +| Medium | Minecraft player | know my friend's email address | contact them when I want to play with them | +| Medium | Minecraft player | know my friend's physical address | go to their houses to play together | +| Medium | Minecraft player | know my friend's country | be mindful of timezone differences when playing | +| Medium | Minecraft player | note down additional information about my friends | remember other noteworthy information about them | +| Medium | Minecraft player | know what servers my friends have been banned from | avoid those servers when playing with them | +| Low | Minecraft player | secure my information behind a password | other people cannot intrude upon my privacy | +| Low | Minecraft player | know my friend's in-game skin | I can recognize them in game | +| Low | Minecraft player | find new friends through Minefriends | have more friends to play Minecraft with | + + + +-------------------------------------------------------------------------------------------------------------------- + +## **Appendix B: Use cases** + +(For all use cases below, the **System** is the `Minefriends` and the **Actor** is the `user`, unless specified otherwise) + +**Use case: Add a friend** + +**MSS** + +1. User requests to add a specific friend in the list +2. Minefriends adds the friend to the list + + Use case ends. + +**Extensions** + +* 1a. The format of the given command is invalid. + + * 1a1. Minefriends shows an error message. + + Use case ends. + +* 1b. The friend already exists in the friend list. -### Product scope + * 1b1. Minefriends shows an error message. + + Use case ends. -**Target user profile**: +**Use case: Delete a friend** -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing to mouse interactions -* is reasonably comfortable using CLI apps +**MSS** -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +1. User requests to list friends +2. Minefriends shows a list of friends +3. User requests to delete a specific friend in the list +4. Minefriends deletes the friend + Use case ends. -### User stories +**Extensions** -Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` +* 2a. The list is empty. -| Priority | As a …​ | I want to …​ | So that I can…​ | -| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- | -| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | -| `* * *` | user | add a new person | | -| `* * *` | user | delete a person | remove entries that I no longer need | -| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list | -| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident | -| `*` | user with many persons in the address book | sort persons by name | locate a person easily | + Use case ends. -*{More to be added}* +* 3a. The given index is invalid. -### Use cases + * 3a1. Minefriends shows an error message. -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) + Use case resumes at step 2. -**Use case: Delete a person** +**Use case: Edit a friend** **MSS** -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. User requests to list friends +2. Minefriends shows a list of friends +3. User requests to edit a specific friend in the list +4. Minefriends edits the friend Use case ends. @@ -304,74 +560,230 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli * 3a. The given index is invalid. - * 3a1. AddressBook shows an error message. + * 3a1. Minefriends shows an error message. + + Use case resumes at step 2. + +* 3b. The format of the given field to edit is invalid. + + * 3b1. Minefriends shows an error message. + + Use case resumes at step 2. + +* 3c. User requests to edit a friend's username to one that belongs to another friend in the list. + + * 3b1. Minefriends shows an error message. + + Use case resumes at step 2. + +**Use case: Find a friend** + +**MSS** + +1. User requests to find a friend in the list using their name as a keyword. +2. Minefriends shows a list of the friends matched. + + Use case ends. + +**Extensions** + +* 1a. The list is empty. + + Use case ends. + +* 2b. The search does not match the given name to any friend in the list. + + * 2b1. Minefriends returns an empty friend list. + + Use case ends. + +**Use case: Suggest friends** + +**MSS** + +1. User requests to suggest friends in the list who matches the given keywords and time intervals. +2. Minefriends shows the list of friends matched. + + Use case ends. + +**Extensions** + +* 1a. The format of the given command is invalid. + + * 1a1. Minefriends shows an error message. Use case resumes at step 2. -*{More to be added}* +* 2a. The search does not match the given fields to any friend in the list. + + * 2a1. Minefriends returns an empty friend list. + + Use case ends. + +**Use case: Enter a command** + +**MSS** + +1. User attempts to enter a command. +2. Minefriends shows a drop-down list of auto-completed command suggestions. + + Use case ends. + +**Extensions** + +* 1a1. User enters an input that does not match any part of the correct commands. + + Use case ends. + + -### Non-Functional Requirements +-------------------------------------------------------------------------------------------------------------------- + +## **Appendix C: Non-functional requirements** 1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. -2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +2. Should be able to hold up to 100 persons without a noticeable sluggishness in performance for typical usage. 3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. +4. Not suitable for platforms with on-screen keyboards as the keyboard popup may block the screen view. +5. Should be able to launch multiple instance of the app on the same device. +6. Should be able to be used by a person who has never used a CLI program before. +7. Not required to handle the sending of messages between friends. +8. Not required to handle the app on mobile platform. +9. Not required to handle the display of the User Interface properly if the window is smaller than 1280 x 720 pixels in resolution. + +-------------------------------------------------------------------------------------------------------------------- + +## **Appendix D: Glossary** + +### Minecraft-related terminologies + + +| Terminology | Definition | +|--------------------|--------------------------------------------------------------------------------------------------| +| Minecraft | An open world sandbox game, [official website](https://www.minecraft.net/en-us) | +| Minefriends | The name of our app | +| Username | The uniquely identifiable Minecraft username of each player | +| Server | A multiplayer Minecraft server | +| Player | A person who plays Minecraft | +| Mojang Studios | The company that created and owns Minecraft | +| Microsoft | The company that bought over Mojang Studios in 2014 | +| Game mode | There are many ways to enjoy Minecraft, and the game mode describes how the game is being played | +| Game type | A synonym for game mode | + +For a complete glossary of Minecraft terms, please visit this page on the +[Minecraft wiki](https://minecraft.fandom.com/wiki/Tutorials/Game_terms). + +### Other terminologies -*{More to be added}* +| Terminology | Definition | +|---------------|--------------------------------------------------------------------------------------------------------------------| +| Mainstream OS | A mainstream desktop operating system, such as Windows, Linux, OS-X | +| Socials | A person's social media account information, such as their Telegram handle, Instagram username or Twitter username | +| CLI | An acronym for "command line interface" | +| GUI | An acronym for "graphical user interface" | -### Glossary -* **Mainstream OS**: Windows, Linux, Unix, OS-X -* **Private contact detail**: A contact detail that is not meant to be shared with others -------------------------------------------------------------------------------------------------------------------- -## **Appendix: Instructions for manual testing** +## **Appendix E: Instructions for manual testing** -Given below are instructions to test the app manually. +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. -
:information_source: **Note:** These instructions only provide a starting point for testers to work on; -testers are expected to do more *exploratory* testing. +
:information_source: + +**Note:** Please follow the test cases **in order** as some of the later test cases +depend on the state resulting from the earlier test cases.
-### Launch and shutdown +### Launch + +Initial launch +1. Download the jar file and copy into an empty folder +2. Double-click the jar file + +**Expected:** Shows a window with an empty contact list, and a help window too if the app is being used for the first time. + +### Adding a friend + +Run `add n/Victoria Tan m/vicky12345 ms/myserver@127.0.0.1 gt/Minecraft BTS gt/survival s/fb@Victoria Tan s/ig@vicky1234 t/amiga +p/+3412345678901 e/victoria.tan@gmail.com a/500 Calle de las Flores, Madrid c/Spain ti/sun@0900-sun@2300 ti/mon@1500-mon@1900` + +**Expected:** Creates a new person called `Victoria Tan` in the friend list, with all the above-listed attributes. + +Run `add n/Alma m/almaaaa1` + +**Expected:** Creates a new person called `Alma` in the friend list, with all the above-listed attributes. + +### Deleting a friend + +Run `delete 2` + +**Expected:** Removes the contact with name `Alma` from the friend list. + +### Editing a friend + +Run `edit 1 a/Plaza Mayor, Sta. Cruz de Tenerife, Canary Islands` + +**Expected:** The address for `Victoria Tan` is replaced with `Plaza Mayor, Sta. Cruz de Tenerife, Canary Islands`. + +Run `edit 1 t/spanish t/french t/noEnglish` + +**Expected:** The tags for `Victoria Tan` are replaced with the aforementioned three. `amiga` is no longer there. + +### Suggesting friends + +Run `add n/Alma m/almaaaa1` + +Run `suggest k/Alma` + +**Expected:** Only `Alma` shows up. -1. Initial launch +Run `suggest dt/mon@1855` - 1. Download the jar file and copy into an empty folder +**Expected:** Only `Victoria Tan` shows up. - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. +Run `list` -1. Saving window preferences +**Expected:** Both friends show up. - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. +### Data persistence - 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. +Exit Minefriends, then reopen it. -1. _{ more test cases …​ }_ +**Expected:** The data from the last session remains the same. -### Deleting a person +### Help Screen -1. Deleting a person while all persons are being shown +Initial launch: `data/addressbook.json` is not present. - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +**Expected:** The help window is opened. The help window should contain a list of commands and their descriptions. - 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. +Run `help` - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. +**Expected:** Help window opens with a list of commands and their descriptions. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous. +## **Appendix F: Effort** -1. _{ more test cases …​ }_ +If the effort taken to implement AB3 was 100, then the effort we put into Minefriends would be 150. -### Saving data +While we did not change the design and architecture that was laid out by AB3 drastically, we ended up modifying the +behaviour of most of its commands, as well as adding many new and unique features to our software. -1. Dealing with missing/corrupted data files +This includes, but is not limited to: - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ +* Redesigning the entire UI to fit the theme of Minecraft +* Redesigning the `Person` model to include Minecraft-related fields +* Redesigning the `Parser` to parse using regex +* Redesigning the `add` command to include more fields +* Redesigning the `edit` command to include more fields +* Adding a new command to `suggest` friends +* Adding and designing a help window to show the user a list of commands +* Adding the autocomplete feature to the command box -1. _{ more test cases …​ }_ +Amongst these changes, we had the hardest time redesigning the `Person` model, as we encountered various bugs after +we had modified it to include additional fields, thus in the end we took many hours to fix more than 200 test cases related to it. +Due to our iterative approach, we had to redesign other features to fit the needs of a redesigned `Person` model. diff --git a/docs/Logging.md b/docs/Logging.md index 5e4fb9bc217..bd21fd0e5fc 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -1,7 +1,4 @@ ---- -layout: page -title: Logging guide ---- +# Logging guide * We are using `java.util.logging` package for logging. * The `LogsCenter` class is used to manage the logging levels and logging destinations. diff --git a/docs/SettingUp.md b/docs/SettingUp.md index 275445bd551..f4434fac681 100644 --- a/docs/SettingUp.md +++ b/docs/SettingUp.md @@ -1,7 +1,4 @@ ---- -layout: page -title: Setting up and getting started ---- +# Setting up and getting started * Table of Contents {:toc} diff --git a/docs/Testing.md b/docs/Testing.md index 8a99e82438a..975686bd11c 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -1,7 +1,4 @@ ---- -layout: page -title: Testing guide ---- +# Testing guide * Table of Contents {:toc} diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 3716f3ca8a4..74a2680ba7a 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,192 +1,479 @@ --- layout: page -title: User Guide +title: 👾 Minefriends User Guide --- -AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps. +
+ +**Before we begin, it is important to note that:**
+* Minefriends is not affiliated with Minecraft, Mojang Studios or Microsoft in any way. +* Minefriends is an independently developed software. It is not a product owned by Mojang Studios or Microsoft. +* Team Minefriends do not own Minecraft, in whole or in part. + +
+ +Ever asked your friends to play Minecraft together only to be blue-ticked? Well, Minefriends is here for you. +Minefriends is an address book for you to find friends to play Minecraft multiplayer with, +at the right time, with the right game modes and on the right servers. + +This user guide will help you get started with Minefriends and get to know what it can do for you. +The guide is meant for Minecraft players who are familiar with the game and how multiplayer in Minecraft works. +No other technical knowledge is required. + +### How to read this guide + +* Text in [blue](#how-to-read-this-guide) are links. You can click on them to navigate to different parts of the guide. +* Text in **bold** are used to place emphasis on certain things. They are noteworthy and important. +* Text in `this` are related to commands and names of things (proper nouns) in Minefriends. +* Tables are used to provide better organisation of command syntax for easy understanding. +
+* Text in a blue box is used to give important additional information about an aspect of Minefriends. +
-* Table of Contents -{:toc} +### Table of contents +To navigate to see a particular command, you can click on the link in blue. +* [Getting started](#getting-started) +* [User interface breakdown](#user-interface-breakdown) +* [Managing your Minecraft friends](#managing-your-minecraft-friends) + * [help](#viewing-help-help) + * [add](#adding-a-friend-add) + * [list](#listing-all-friends-list) + * [edit](#editing-a-friends-information-edit) + * [find](#locating-friends-by-name-find) + * [suggest](#suggest-me-a-friend-suggest) + * [delete](#deleting-a-friend-delete) + * [clear](#clearing-all-entries-clear) + * [exit](#exiting-the-program-exit) +* [Other features](#other-features) + * [autocomplete](#autocomplete) + * [saving](#saving-the-data) +* [FAQ](#faq) +* [Summary of commands](#command-summary) +* [Glossary](#glossary) -------------------------------------------------------------------------------------------------------------------- -## Quick start +## Getting started 1. Ensure you have Java `11` or above installed in your Computer. +2. Download the latest `minefriends.jar` [here](https://github.com/AY2223S1-CS2103T-T10-4/tp/releases). +3. Create a folder called `Minefriends`. +4. Put the `minefriends.jar` file inside the folder. +5. Double-click the file to start the app. A window similar to the one below should appear in a few seconds.
+ ![Ui](images/StartupUi.png) -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). - -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +
-1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png) +**Important Note:**
+The window is empty because no friends have been added yet. Adding more friends will change the look of the window. +
-1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try: +* Type the command in the [command box](#user-interface-breakdown) and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- * **`list`** : Lists all contacts. +Some sample commands you can try out: - * **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. + * **`list`** : Lists all of your friends. + * **`add`**`n/Victoria Tan m/vicky12345 p/85355255 e/vic@gmail.com a/123, Jurong West Ave 6, #08-111` :
+ Adds a friend named `Victoria Tan` to your friend list. + * **`delete`**`3` : Deletes the 3rd friend shown in your current friend list. + +Refer to the [Managing your Minecraft friends](#managing-your-minecraft-friends) below for details of each command. - * **`delete`**`3` : Deletes the 3rd contact shown in the current list. +-------------------------------------------------------------------------------------------------------------------- - * **`clear`** : Deletes all contacts. +## User interface breakdown - * **`exit`** : Exits the app. +![GUI](images/GUIExplainer.png) -1. Refer to the [Features](#features) below for details of each command. +1. **Topbar**. You can find access the help screen here. +2. **Command box**. You can type in your commands here. +3. **Command result display** You can see the results of your command execution here. +4. **List of friends** You can see all your Minecraft friends listed here. +5. **Data file name** You can see where the data is being loaded from. -------------------------------------------------------------------------------------------------------------------- -## Features +## Managing your Minecraft friends
-**:information_source: Notes about the command format:**
- -* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. +**Command format legend:**
-* Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. +* You need to replace the `UPPER_CASE` fields with your desired words.
+ e.g. in `add n/NAME m/MINECRAFT_NAME`, you can replace `NAME` and `MINECRAFT_NAME` with names of your choice.
+ e.g. `add n/Amy Bee m/amybee123` -* Items with `…`​ after them can be used multiple times including zero times.
- e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. +* Items in square brackets are optional and can be left out.
+ e.g. `n/NAME [t/TAG]` can be used as `n/Amy Bee t/friend` or as `n/Amy Bee`. -* Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +* For items with a `*`, you can provide more than one of such item.
+ e.g. `n/NAME [t/TAG]*` can be used as `n/Amy Bee`, `n/Amy Bee t/friend` or `n/Amy Bee t/friend t/bestie`. -* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
- e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken. +* You can provide the fields in any order.
+ e.g. if the command specifies the fields to be in the order`n/NAME p/PHONE_NUMBER`, + arranging them in an alternative order such as `p/PHONE_NUMBER n/NAME` is also acceptable. -* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
+* Extra words for standalone commands (such as `help`, `list`, `exit` and `clear`) will be ignored.
e.g. if the command specifies `help 123`, it will be interpreted as `help`.
-### Viewing help : `help` - -Shows a message explaning how to access the help page. - -![help message](images/helpMessage.png) +### Viewing help: `help` Format: `help` +In the `Help` window, upon choosing a specific command, a description +and an example will be shown for that specific command along with the +specific parameters for that command. -### Adding a person: `add` +
-Adds a person to the address book. +**Important Note:**
+* The help window will be launched automatically on startup for new users +(or if you do not have the file data/addressbook.json) to view the commands +available in Minefriends. -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +* Subsequent launches of Minefriends will not show the help window on launch. -
:bulb: **Tip:** -A person can have any number of tags (including 0)
-Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +Example:
+Upon typing `help` or clicking on the `help` option, a help window +as such will appear: -### Listing all persons : `list` + -Shows a list of all persons in the address book. +You can choose a specific command by clicking on the drop-down menu in order to view the +information for that command. -Format: `list` +### Adding a friend: `add` +Format (fields in `[ ]` are optional, `*` indicates multiple entry): `add n/NAME m/MINECRAFT_NAME [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] +[c/COUNTRY] [ms/MINECRAFT_SERVER]* [s/SOCIAL_HANDLES]* [t/TAG]* [gt/GAME_TYPE]* [ti/TIME_INTERVAL]*` -### Editing a person : `edit` +This command allows you to add a person to your friend list. -Edits an existing person in the address book. +#### Details of each field: -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +The following table lists all the fields that you can include when you add your friends, +it also provides you with additional information for your understanding. -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ -* At least one of the optional fields must be provided. +| Field | Command | Status | Explanation | +| ----- | --- | --- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Name | `n/NAME` | **Compulsory** | Name of your friend | +| Minecraft name | `m/MINECRAFT_NAME` | **Compulsory, Unique** | In-game username of your friend. Minecraft usernames cannot contain spaces. Minecraft usernames are used to identify your friends uniquely in Minefriends (and Minecraft too), so they must be unique.

If you type in `add n/John Doe` without the friend's Minecraft name, there will be an error thrown. Similarly, if you type in `add m/johndoe123` without the friend's name, there will be an error thrown. | +| Phone Number | `[p/PHONE_NUMBER]` | **Optional** | Contact number of your friend. | +| Email | `[e/EMAIL]` | **Optional** | Email address of your friend | +| Address | `[a/ADDRESS]` | **Optional** | Physical address of your friend | +| Country | `[c/COUNTRY]` | **Optional** | The country in which your friend resides in.

There should **not** be any numbers in the country | + | Minecraft server | `[ms/MINECRAFT_SERVER]*` | **Optional, Multiple** | The minecraft servers that your friend plays on.

Minecraft servers are in the format of `ServerName@ServerAddress`. | +| Social Handle | `[s/SOCIAL]*` | **Optional, Multiple** | The social media handles of your friend, such as their Instagram username, Telegram handle etc.

Social handles are in the format of `SocialMedia@Username`. | +| Game Type | `[gt/GAME_TYPE]*` | **Optional, Multiple** | Game types refer to the different ways to play multiplayer Minecraft.

They can be anything, such as `survival games`, `skyblock` or `creative`. | +| Tag | `[t/TAG]*` | **Optional, Multiple** | Tags are used to provide additional information about your friend.

They can be anything, as long as there are no spaces and are alphanumeric. | +| Time Interval | `[ti/TIME_INTERVAL]*` | **Optional, Multiple** | This refers to the timings in which your friend is available.

Time interval comes in the format `day@hhmm-day@hhmm`, such as `mon@2100-mon@2300`, with the hours and minutes in 24-hour format. The first date-time being the start and the second date-time being the end of their availability period. `day` can be `mon`, `tue`, `wed`, `thu`, `fri`, `sat` or `sun` only.

If the start and end time interval for a friend are the same, it is treated as one point in time at that exact moment. To indicate availability across the whole week, use `mon@0000-sun@2359`. | + +
+ +**Important note:**
+If more than one of the non-multiple fields are provided
+eg. `add m/vicky n/Victor n/Victoria`
+only the last one `Victoria` will be taken. + +
+ +#### Examples: + +##### Adding with a few fields +* Before adding `add n/Amy Bee m/AmyBee123 p/85355255 e/amy@gmail.com a/123, Jurong West Ave 6, #08-111` +![BeforeAdding](images/BeforeAddingAmy.png) +
+
+
+ +* After adding `add n/Amy Bee m/AmyBee123 p/85355255 e/amy@gmail.com a/123, Jurong West Ave 6, #08-111` +![AfterAdding](images/AfterAddingAmy.png) +
+
+
+##### Adding with several optional fields + +* Before adding `add n/john lee p/92990123 m/johnissmart a/20 colorado drive +e/johnisgood@gmail.com c/China t/hot t/nice t/smart s/insta@HotJohn s/fb@JohnIsHot` +![BeforeAdding](images/BeforeAddingJohn.png) + +
+
+
+ +* After adding `add n/john lee p/92990123 m/johnissmart a/20 colorado drive +e/johnisgood@gmail.com c/China t/hot t/nice t/smart s/insta@HotJohn s/fb@JohnIsHot` +![AfterAdding](images/AfterAddingJohn.png) +
+
+
+ +### Editing a friend's information: `edit` +Format: `edit INDEX [n/NAME] [m/MINECRAFT_NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] +[c/COUNTRY] [ms/MINECRAFT_SERVER]* [s/SOCIAL_HANDLES]* [t/TAG]* [gt/GAME_TYPE]* [ti/TIME_INTERVAL]*`
+ +If you want to change an existing attribute of a friend, or fill in optional attributes that were not provided during the `add` process, +you can use the `edit` command to easily change that. + +The following explains what you should do to edit a particular person. +* Edits the person at the specified `INDEX`. The index refers to the index number shown in your displayed friend list. The index **must be a positive integer** 1, 2, 3, …​ +* At least one field must be provided for editing. * Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. +* You should not write an **invalid** index. + * If you have three friends in your friend list, `edit 4 ...` will result in an error. +
-Examples: -* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +**Important Note:**
+* Editing a field with multiple entries will **overwrite all existing values of that field**.
+ e.g. `edit 1 t/amiga` will remove all existing tags of the first person and replace them with only `amiga` -### Locating persons by name: `find` +* For fields with multiple entries, you can remove all the person’s information in that field by typing the prefix (eg: `t/`) without +including any information after the prefix. -Finds persons whose names contain any of the given keywords. +
-Format: `find KEYWORD [MORE_KEYWORDS]` +#### Examples: -* The search is case-insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). - e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +##### Editing only single-entry fields +Command: `edit 2 p/91234567 e/amybee123@gmail.com`
-Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +Edits the phone number and email address of the 2nd person in the list +to be `91234567` and `amybee123@gmail.com` respectively.
+ +Before editing +![BeforeEditAmyCompulsory](images/BeforeEditAmyCompulsory.png) +
+
+
+After editing +![AfterEditAmyCompulsory](images/AfterEditAmyCompulsory.png) + +##### Editing both single-entry and multi-entry fields +Command: `edit 2 n/Amy Bee t/`
+ +Edits the name of the 2nd person in the list to be `Amy Bee` +and clears all existing tags. + +Before editing +![BeforeEditAmyOptional](images/BeforeEditAmyOptional.png) +
+
+
+After editing +![AfterEditAmyOptional](images/AfterEditAmyOptional.png) + +### Deleting a friend: `delete` +Format: `delete INDEX`
+ +You can remove a specified friend from your friend list. + +* Deletes the friend at the specified `INDEX` of your friend list. +* The index **must be a positive integer** 1, 2, 3, … +* You should not write an **invalid** index. + * If you have three friends in your friend list, `delete 4` will result in an error. + +Example: +* `list` followed by `delete 2` deletes the 2nd friend in your friend list.
+ +Before delete +![BeforeDelete](images/BeforeDelete.png) -### Deleting a person : `delete` +After delete +![AfterDelete](images/AfterDelete.png) -Deletes the specified person from the address book. +### Locating friends by name: `find` +Format: `find KEYWORD [MORE_KEYWORDS]`
-Format: `delete INDEX` +Finds friends whose names contain any of the given keywords. +You can use this command if you remember their names. Otherwise, the [suggest](#suggest-me-a-friend-suggest) +command is a better option. -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index **must be a positive integer** 1, 2, 3, …​ +* The search is case-insensitive. (e.g. `amy` will match `Amy`)
+
+Example:
+`find Amy` returns `amy` and `Amy Bee` +![FindAmy](images/FindAmy.png) + +* The order of the keywords does not matter. e.g. `Amy Bee` will match `Bee Amy`. +* You can only search for someone's **name**. +* Only **full words** will be matched. (e.g. `Am` will not match `Amy`) + +* Persons matching at least one keyword will be returned + (e.g. `Amy Tan` will return `Amy Bee` and `Benson Tan`) +
+Example:
+`find amy benson` returns `Amy Bee` and `Benson Tan`
+![FindAmyBenson](images/FindAmyBenson.png) + +### Suggest me a friend: `suggest` + +Suggest friends to play with given a set of constraints. +This command is very versatile. You can find friends based on any attribute of them, including +their available timings to play. + +Format: `suggest [dt/DAY_TIME_IN_WEEK]* [k/KEYWORD]*` + +* At least one instance of either `DAY_TIME_IN_WEEK` or `KEYWORD` must be provided +* The search is case-insensitive (e.g. `amy` will match `Amy`) +* Keywords do not need to be in full (e.g. `just` will match `Justin`) +* The order of the keywords does not matter +* The `Keyword` will be matched against **all** attributes of a friend (eg. Name, Minecraft Name, Address etc.) +* As long as some attribute of a friend contains the `Keyword`, the `Keyword` is considered to have a valid match +* All `Keyword` must be matched, but only 1 `DayTimeInWeek` needs to be matched +* If you wish to find a friend playing at the same time as you, you can type in `suggest dt/now` + +
+ +**Important Note:**
+When you type in `suggest dt/now`, Minefriends uses the current time shown on your computer/device +to search for friends playing at that current time. + +
Examples: -* `list` followed by `delete 2` deletes the 2nd person in the address book. -* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command. -### Clearing all entries : `clear` +`suggest dt/tue@2125 dt/sat@1200 k/Victoria k/Vicky` +will return friends subjected to **all** of the following conditions: +1. Is available at either Tuesday 9:25pm or Saturday 12:00pm +2. Attributes contain the `Keyword` `Victoria` (ignore case) +3. Attributes contain the `Keyword` `Vicky` (ignore case) + +* A friend with name `Victoria Tan` and Minecraft name `vicky12345`, who is available from Tuesday 7pm to 11pm +will be matched +* A friend with name `Victoria Tan` but no other attributes containing `Vicky` will not be matched +* A friend with name `Victoria Tan` and Minecraft name `vicky12345`, who is only available from Sunday 1pm to 6pm +will also not be matched + +`suggest k/Victoria` +will return friends subjected to the only condition: +* Attributes contain the `Keyword` `Victoria` (ignore case) + +`suggest dt/tue@2125` +will return friends subjected to the only condition: +* Is available at Tuesday 9:25pm + +If you type `suggest dt/now` on Sunday 8:00pm, you will return friends subjected to the only condition: +* Is available at Sunday 8:00pm + +### Listing all friends: `list` +Format: `list`
+ +Shows a list of all of your Minecraft friends. This command +should be run after a `suggest` or `find` to list out all your friends. + +
+ +A `suggest` or `find` will filter out your list of friends to produce of shorter list, +so `list` will list out all your friends again. -Clears all entries from the address book. +
+ +Example: + +![ListExample](images/ListExample.png) + +### Clearing all entries: `clear` + +Clears all entries from your friend list. Format: `clear` -### Exiting the program : `exit` + +### Exiting the program: `exit` Exits the program. Format: `exit` -### Saving the data +## Other features -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +### Autocomplete -### Editing the data file +Minefriends supports autocomplete for all commands. Simply press the `TAB` or `ENTER` key to autocomplete the command. -AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +The following picture shows the autocomplete feature in action: +![Autocomplete](images/Autocomplete.png) -
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. -
+In order to navigate the list of autocomplete suggestions, you can either use the `UP` and `DOWN` arrow keys, +or simply click on the desired suggestion. -### Archiving data files `[coming in v2.0]` +### Saving the data -_Details coming soon ..._ +Minefriends data are saved in the hard disk automatically after any +command that changes the data. There is no need to save manually. -------------------------------------------------------------------------------------------------------------------- ## FAQ -**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. +**Q: How do I transfer my Minefriends data to another Computer?**
+ +**A**: +Install the app in the other computer and overwrite the empty data file it creates with the file that +contains the data of your previous Minefriends home folder. + +**Q: What is the recommended window size?**
+ +**A**: +The recommended window size is 1280 x 720 pixels, anything smaller may cause the UI to be distorted. + +**Q: How long can the parameters be?**
+ +**A**: +As long as you want, however please note that the UI will be distorted if these parameters are too long. + +**Q: Why do I need to press `Enter` twice to input the command?**
+ +**A**: +Unfortunately, this is a limitation of ControlsFX, which is a third party library for the autocomplete feature. +The first `Enter` key press will trigger the autocomplete feature, +the second `Enter` key press will execute the command. -------------------------------------------------------------------------------------------------------------------- ## Command summary -Action | Format, Examples ---------|------------------ -**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -**Clear** | `clear` -**Delete** | `delete INDEX`
e.g., `delete 3` -**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`
e.g.,`edit 2 n/James Lee e/jameslee@example.com` -**Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` -**List** | `list` -**Help** | `help` +| Action | Definition | Format | Examples | +|-------------|-----------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------| +| **Add** | Adds a new friend into Minefriends | `add n/NAME m/MINECRAFT_NAME [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [c/COUNTRY] [ms/MINECRAFT_SERVER]* [s/SOCIAL_HANDLES]* [t/TAG]* [gt/GAME_TYPE]* [ti/TIME_INTERVAL]*` | `add n/Benson m/benson01 p/92881083 e/bensontan@hotmail.com a/ 4 Leith road s/ig@bensontan01 t/bff` | +| **List** | Lists out all of your friends in Minefriends | `list` | | +| **Edit** | Change some details of a certain friend | `edit INDEX [n/NAME] [m/MINECRAFT_NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [c/COUNTRY] [ms/MINECRAFT_SERVER]* [s/SOCIAL_HANDLES]* [t/TAG]* [gt/GAME_TYPE]* [ti/TIME_INTERVAL]*` | `edit 2 n/Amy Bee t/` | | +| **Find** | Find a certain friend (or friends) by their names | `find KEYWORD [MORE_KEYWORDS]*` | `find Amy Benson` | +| **Delete** | Delete a certain friend from Minefriends | `delete INDEX` | `delete 3` | +| **Suggest** | Filter out friends by keywords or by a day and time | `suggest [dt/DAY_TIME_IN_WEEK]* [k/KEYWORD]*` | `suggest dt/tue@2125 dt/sat@1200 k/Victoria k/Vicky` | +| **Clear** | Clear all friends from Minefriends | `clear` | | +| **Exit** | Leave the application | `exit` | | +| **Help** | Get help on how to use the application | `help` | | + +-------------------------------------------------------------------------------------------------------------------- + +## Glossary + +### Minecraft-related terminologies + +| Terminology | Definition | +|--------------------|--------------------------------------------------------------------------------------------------| +| Minecraft | An open world sandbox game, [official website](https://www.minecraft.net/en-us) | +| Minefriends | The name of our app | +| Username | The uniquely identifiable Minecraft username of each player | +| Server | A multiplayer Minecraft server | +| Player | A person who plays Minecraft | +| Mojang Studios | The company that created and owns Minecraft | +| Microsoft | The company that bought over Mojang Studios in 2014 | +| Game mode | There are many ways to enjoy Minecraft, and the game mode describes how the game is being played | +| Game type | A synonym for game mode | + +For a complete glossary of Minecraft terms, please visit this page on the +[Minecraft wiki](https://minecraft.fandom.com/wiki/Tutorials/Game_terms). + +### Other terminologies + +| Terminology | Definition | +|---------------|--------------------------------------------------------------------------------------------------------------------| +| Mainstream OS | A mainstream desktop operating system, such as Windows, Linux, OS-X | +| Socials | A person's social media account information, such as their Telegram handle, Instagram username or Twitter username | +| CLI | An acronym for "command line interface" | +| GUI | An acronym for "graphical user interface" | diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..107b60e8d2e 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "Minefriends" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2223S1-CS2103T-T10-4/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..aadf4dc2eb2 100644 --- a/docs/_sass/minima/_base.scss +++ b/docs/_sass/minima/_base.scss @@ -288,7 +288,7 @@ table { text-align: center; } .site-header:before { - content: "AB-3"; + content: "Minefriends"; font-size: 32px; } } diff --git a/docs/diagrams/AutoCompleteActivityDiagram.puml b/docs/diagrams/AutoCompleteActivityDiagram.puml new file mode 100644 index 00000000000..8139c2d45a4 --- /dev/null +++ b/docs/diagrams/AutoCompleteActivityDiagram.puml @@ -0,0 +1,24 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR_T2 +skinparam classBackgroundColor MODEL_COLOR_T2 + +(*) --> "User types into CommandBox" + +--> "AutoComplete matches user input against list of commands" + +if "AutoComplete finds a match" then + -->[yes] "AutoComplete displays the matching command in the AutoCompletePopup" + --> "User presses TAB key" + --> "AutoComplete replaces the user input with the match" + --> "User presses ENTER key" + --> "Command is executed" + --> (*) +else + -->[no] "AutoComplete displays no commands in the AutoCompletePopup" + --> (*) + endif + + +@enduml diff --git a/docs/diagrams/EditCommand.puml b/docs/diagrams/EditCommand.puml new file mode 100644 index 00000000000..307a4731956 --- /dev/null +++ b/docs/diagrams/EditCommand.puml @@ -0,0 +1,30 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR +skinparam classBackgroundColor LOGIC_COLOR + +class "{abstract}\nCommand" as Command +class "<>\nModel" as Model +class EditCommand +class ModelManager +class EditPersonDescriptor + +Command <|- EditCommand +Model <|- ModelManager +EditCommand *-- "1" EditPersonDescriptor +ModelManager <-down- EditCommand + +abstract class Model { + void setPerson(Person target, Person editedPerson) +} + +abstract class Command { + CommandResult execute(Model model) +} + +class EditCommand { + CommandResult execute(Model model +} + +@enduml diff --git a/docs/diagrams/EditCommandSequenceDiagram.puml b/docs/diagrams/EditCommandSequenceDiagram.puml new file mode 100644 index 00000000000..98a474e92bb --- /dev/null +++ b/docs/diagrams/EditCommandSequenceDiagram.puml @@ -0,0 +1,58 @@ +@startuml +'https://plantuml.com/sequence-diagram +!include style.puml + +participant ":EditCommand" as EditCommand LOGIC_COLOR +participant ":Model" as Model MODEL_COLOR +participant "lastShownList:List" as List MODEL_COLOR +participant ":Person" as Person MODEL_COLOR + +[-> EditCommand: execute(model) +activate EditCommand + +EditCommand -> Model: getFilteredPersonList() +activate Model + +Model -> List: get(index.getZeroBased()) +activate List + +List -> Person +activate Person + +Person --> List: personToEdit +deactivate Person + +List --> Model: personToEdit +deactivate List + +Model --> EditCommand: personToEdit +deactivate Model + +EditCommand -> Model: createEditedPerson(personToEdit, editPersonDescriptor) +activate Model + +Model -> Person +activate Person + +Person --> Model: editedPerson +deactivate Person + +Model --> EditCommand: editedPerson +deactivate Model + +EditCommand -> Model: setPerson(personToEdit, editedPerson) +activate Model + +Model --> EditCommand +deactivate Model + +EditCommand -> Model: updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS) +activate Model + +Model --> EditCommand +deactivate Model + +[<-- EditCommand: CommandResult +deactivate EditCommand + +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 4439108973a..1094829b2a0 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -17,9 +17,14 @@ Class Person Class Address Class Email Class Name +Class MinecraftName +Class Country Class Phone Class Tag - +Class GameType +Class Server +Class Social +Class TimeInterval } Class HiddenOutside #FFFFFF @@ -36,11 +41,17 @@ UserPrefs .up.|> ReadOnlyUserPrefs AddressBook *--> "1" UniquePersonList UniquePersonList --> "~* all" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address +Person *--> "1" Name +Person *--> "1" MinecraftName +Person *--> "0..1" Country +Person *--> "0..1" Phone +Person *--> "0..1" Email +Person *--> "0..1" Address Person *--> "*" Tag +Person *--> "*" GameType +Person *--> "*" Server +Person *--> "*" Social +Person *--> "*" TimeInterval Name -[hidden]right-> Phone Phone -[hidden]right-> Address diff --git a/docs/diagrams/PersonTagClassDiagram.puml b/docs/diagrams/PersonTagClassDiagram.puml new file mode 100644 index 00000000000..744e3f012cd --- /dev/null +++ b/docs/diagrams/PersonTagClassDiagram.puml @@ -0,0 +1,29 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +abstract Class "{abstract}\nUIPart" +Class PersonCard +Class PersonListPanel +Class HBox +Class Label +Class FlowPane +Class Person +Class MainWindow + +MainWindow *-- "1" "PersonListPanel" +PersonListPanel --> "*" PersonCard +"PersonCard" --> "*" "HBox" +"PersonCard" --> "*" "Label" +"PersonCard" --> "*" "FlowPane" +"{abstract}\nUIPart" <|-- PersonCard +"{abstract}\nUIPart" <|-- PersonListPanel +PersonCard ..> Person + +Class Person { +toDisplayString() +} + +@enduml diff --git a/docs/diagrams/ServerRepresentationSequenceDiagram.puml b/docs/diagrams/ServerRepresentationSequenceDiagram.puml new file mode 100644 index 00000000000..e6d69c17ab0 --- /dev/null +++ b/docs/diagrams/ServerRepresentationSequenceDiagram.puml @@ -0,0 +1,35 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":AddCommandParser" as AddCommandParser LOGIC_COLOR +participant ":ParserUtil" as ParserUtil LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Person" as Person LOGIC_COLOR +end box + +[-> AddCommandParser: parse(args) +activate AddCommandParser + +AddCommandParser -> ParserUtil: parseServers(Servers) +activate ParserUtil + +loop for each Server in Servers + ParserUtil -> ParserUtil: parseServer(Server) + activate ParserUtil + ParserUtil --> ParserUtil: Server + deactivate ParserUtil +end +ParserUtil --> AddCommandParser: ServerSet +deactivate ParserUtil + +AddCommandParser -> Person: Person +activate Person +Person --> AddCommandParser +deactivate Person +[<-- AddCommandParser: addCommand(Person) +deactivate AddCommandParser + +@enduml diff --git a/docs/diagrams/SuggestFriendClassDiagram.puml b/docs/diagrams/SuggestFriendClassDiagram.puml new file mode 100644 index 00000000000..413734ea5b8 --- /dev/null +++ b/docs/diagrams/SuggestFriendClassDiagram.puml @@ -0,0 +1,36 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor LOGIC_COLOR +skinparam classBackgroundColor LOGIC_COLOR + +class "<>\nPredicate" as Predicate +class "{abstract}\nCommand" as Command +class SuggestCommand +class PersonSuggestionPredicate +class DayTimeInWeek +class Keyword + +Predicate <|- PersonSuggestionPredicate +Command <|- SuggestCommand +SuggestCommand *-- "1" PersonSuggestionPredicate +PersonSuggestionPredicate *-- "0..*" DayTimeInWeek +PersonSuggestionPredicate *-- "0..*" Keyword + +class Command { + CommandResult execute(Model model) +} + +class Predicate { + boolean test() +} + +class SuggestCommand { + CommandResult execute(Model model) +} + +class PersonSuggestionPredicate { + boolean test(Person person) +} + +@enduml diff --git a/docs/diagrams/SuggestFriendSequenceDiagram.puml b/docs/diagrams/SuggestFriendSequenceDiagram.puml new file mode 100644 index 00000000000..ac1ee28c610 --- /dev/null +++ b/docs/diagrams/SuggestFriendSequenceDiagram.puml @@ -0,0 +1,27 @@ +@startuml +'https://plantuml.com/sequence-diagram +!include style.puml + +participant ":SuggestCommand" as SuggestCommand LOGIC_COLOR +participant ":Model" as Model MODEL_COLOR +participant "filteredPersons:FilteredList" as FilteredList MODEL_COLOR + +[-> SuggestCommand: execute(model) +activate SuggestCommand + +SuggestCommand -> Model: updateFilteredPersonList(predicate) +activate Model + +Model -> FilteredList: setPredicate(predicate) +activate FilteredList + +Model <-- FilteredList +deactivate FilteredList + +SuggestCommand <-- Model +deactivate Model + +[<-- SuggestCommand: CommandResult +deactivate SuggestCommand + +@enduml diff --git a/docs/diagrams/SuggestNowSequenceDiagram.puml b/docs/diagrams/SuggestNowSequenceDiagram.puml new file mode 100644 index 00000000000..cf6cd806762 --- /dev/null +++ b/docs/diagrams/SuggestNowSequenceDiagram.puml @@ -0,0 +1,43 @@ +@startuml +!include style.puml +participant ":ParserUtil" as ParserUtil LOGIC_COLOR +participant ":CurrentTimeConverter" as CurrentTimeConverter LOGIC_COLOR +participant "currentTime:LocalDateTime" as currentTime LOGIC_COLOR + +[-> ParserUtil: parseDayTimeInWeek(dayTimeInWeek) +activate ParserUtil + +ParserUtil -> CurrentTimeConverter: findTimeNowInString() +activate CurrentTimeConverter + +CurrentTimeConverter -> currentTime: now() +activate currentTime + +currentTime -> currentTime: getDayOfWeek() +activate currentTime + +currentTime --> currentTime: currentDay +deactivate currentTime + +currentTime --> CurrentTimeConverter +deactivate currentTime + +CurrentTimeConverter -> CurrentTimeConverter: convertDayOfWeekToString() +activate CurrentTimeConverter + +CurrentTimeConverter --> CurrentTimeConverter: currentDayInString +deactivate CurrentTimeConverter + +CurrentTimeConverter -> currentTime: format() +activate currentTime + +currentTime --> CurrentTimeConverter: formattedTime +deactivate currentTime + +CurrentTimeConverter --> ParserUtil +deactivate CurrentTimeConverter + +[<-- ParserUtil: DayTimeInWeek +deactivate ParserUtil + +@enduml diff --git a/docs/diagrams/TimeIntervalClassDiagram.puml b/docs/diagrams/TimeIntervalClassDiagram.puml new file mode 100644 index 00000000000..6e92bb4570f --- /dev/null +++ b/docs/diagrams/TimeIntervalClassDiagram.puml @@ -0,0 +1,20 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +class Person +class TimeInterval +class DayTimeInWeek +interface "<> \n ITimesAvailable" + +"<> \n ITimesAvailable" <|.. TimeInterval +Person *--> " *" TimeInterval +TimeInterval *-- "2 " DayTimeInWeek + +interface "<> \n ITimesAvailable" { +isAvailable(DayTimeInWeek) +} + +@enduml diff --git a/docs/images/AfterAddingAmy.png b/docs/images/AfterAddingAmy.png new file mode 100644 index 00000000000..322e91034ae Binary files /dev/null and b/docs/images/AfterAddingAmy.png differ diff --git a/docs/images/AfterAddingJohn.png b/docs/images/AfterAddingJohn.png new file mode 100644 index 00000000000..1cdff5a66a0 Binary files /dev/null and b/docs/images/AfterAddingJohn.png differ diff --git a/docs/images/AfterDelete.png b/docs/images/AfterDelete.png new file mode 100644 index 00000000000..b20c5a0e893 Binary files /dev/null and b/docs/images/AfterDelete.png differ diff --git a/docs/images/AfterEditAmyCompulsory.png b/docs/images/AfterEditAmyCompulsory.png new file mode 100644 index 00000000000..88cec409c4e Binary files /dev/null and b/docs/images/AfterEditAmyCompulsory.png differ diff --git a/docs/images/AfterEditAmyOptional.png b/docs/images/AfterEditAmyOptional.png new file mode 100644 index 00000000000..43bd4656592 Binary files /dev/null and b/docs/images/AfterEditAmyOptional.png differ diff --git a/docs/images/AutoCompleteActivityDiagram.png b/docs/images/AutoCompleteActivityDiagram.png new file mode 100644 index 00000000000..78434fcd15e Binary files /dev/null and b/docs/images/AutoCompleteActivityDiagram.png differ diff --git a/docs/images/Autocomplete.png b/docs/images/Autocomplete.png new file mode 100644 index 00000000000..67e33f0f97c Binary files /dev/null and b/docs/images/Autocomplete.png differ diff --git a/docs/images/BeforeAddingAmy.png b/docs/images/BeforeAddingAmy.png new file mode 100644 index 00000000000..0023c4cd8a1 Binary files /dev/null and b/docs/images/BeforeAddingAmy.png differ diff --git a/docs/images/BeforeAddingJohn.png b/docs/images/BeforeAddingJohn.png new file mode 100644 index 00000000000..f3defc6d481 Binary files /dev/null and b/docs/images/BeforeAddingJohn.png differ diff --git a/docs/images/BeforeDelete.png b/docs/images/BeforeDelete.png new file mode 100644 index 00000000000..3da2cb8e54a Binary files /dev/null and b/docs/images/BeforeDelete.png differ diff --git a/docs/images/BeforeEditAmyCompulsory.png b/docs/images/BeforeEditAmyCompulsory.png new file mode 100644 index 00000000000..b2af5e27efe Binary files /dev/null and b/docs/images/BeforeEditAmyCompulsory.png differ diff --git a/docs/images/BeforeEditAmyOptional.png b/docs/images/BeforeEditAmyOptional.png new file mode 100644 index 00000000000..355f569fe81 Binary files /dev/null and b/docs/images/BeforeEditAmyOptional.png differ diff --git a/docs/images/EditCommandSequenceDiagram.png b/docs/images/EditCommandSequenceDiagram.png new file mode 100644 index 00000000000..ba16a387d09 Binary files /dev/null and b/docs/images/EditCommandSequenceDiagram.png differ diff --git a/docs/images/FindAmy.png b/docs/images/FindAmy.png new file mode 100644 index 00000000000..9530d9bde08 Binary files /dev/null and b/docs/images/FindAmy.png differ diff --git a/docs/images/FindAmyBenson.png b/docs/images/FindAmyBenson.png new file mode 100644 index 00000000000..355eecb898f Binary files /dev/null and b/docs/images/FindAmyBenson.png differ diff --git a/docs/images/GUIExplainer.png b/docs/images/GUIExplainer.png new file mode 100644 index 00000000000..27ed6c5471e Binary files /dev/null and b/docs/images/GUIExplainer.png differ diff --git a/docs/images/HelpWindow.png b/docs/images/HelpWindow.png new file mode 100644 index 00000000000..14dada0f2fa Binary files /dev/null and b/docs/images/HelpWindow.png differ diff --git a/docs/images/ListExample.png b/docs/images/ListExample.png new file mode 100644 index 00000000000..f0fb5944ae0 Binary files /dev/null and b/docs/images/ListExample.png differ diff --git a/docs/images/MSRepSeqDiagram.png b/docs/images/MSRepSeqDiagram.png new file mode 100644 index 00000000000..0542a9cca81 Binary files /dev/null and b/docs/images/MSRepSeqDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 04070af60d8..b7fb79b8c2b 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/PersonTagClassDiagram.png b/docs/images/PersonTagClassDiagram.png new file mode 100644 index 00000000000..ac2d3201977 Binary files /dev/null and b/docs/images/PersonTagClassDiagram.png differ diff --git a/docs/images/ServerRepresentationSequenceDiagram.png b/docs/images/ServerRepresentationSequenceDiagram.png new file mode 100644 index 00000000000..b70c59bc14c Binary files /dev/null and b/docs/images/ServerRepresentationSequenceDiagram.png differ diff --git a/docs/images/StartupUi.png b/docs/images/StartupUi.png new file mode 100644 index 00000000000..41d283580b9 Binary files /dev/null and b/docs/images/StartupUi.png differ diff --git a/docs/images/SuggestFriendClassDiagram.png b/docs/images/SuggestFriendClassDiagram.png new file mode 100644 index 00000000000..c77091acd52 Binary files /dev/null and b/docs/images/SuggestFriendClassDiagram.png differ diff --git a/docs/images/SuggestFriendSequenceDiagram.png b/docs/images/SuggestFriendSequenceDiagram.png new file mode 100644 index 00000000000..72d266cc1bc Binary files /dev/null and b/docs/images/SuggestFriendSequenceDiagram.png differ diff --git a/docs/images/SuggestNowSequenceDiagram.png b/docs/images/SuggestNowSequenceDiagram.png new file mode 100644 index 00000000000..d73aee8677c Binary files /dev/null and b/docs/images/SuggestNowSequenceDiagram.png differ diff --git a/docs/images/TimeIntervalClassDiagram.png b/docs/images/TimeIntervalClassDiagram.png new file mode 100644 index 00000000000..c0884083a56 Binary files /dev/null and b/docs/images/TimeIntervalClassDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..cc5acd18929 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/chustinjeng.png b/docs/images/chustinjeng.png new file mode 100644 index 00000000000..d13419d229e Binary files /dev/null and b/docs/images/chustinjeng.png differ diff --git a/docs/images/editCommand.png b/docs/images/editCommand.png new file mode 100644 index 00000000000..6b2d702a6d8 Binary files /dev/null and b/docs/images/editCommand.png differ diff --git a/docs/images/onepersonhere.png b/docs/images/onepersonhere.png new file mode 100644 index 00000000000..4c1954122b3 Binary files /dev/null and b/docs/images/onepersonhere.png differ diff --git a/docs/images/shawnkai.png b/docs/images/shawnkai.png new file mode 100644 index 00000000000..6ac7485f80b Binary files /dev/null and b/docs/images/shawnkai.png differ diff --git a/docs/images/sherylkong18.png b/docs/images/sherylkong18.png new file mode 100644 index 00000000000..c2026f57273 Binary files /dev/null and b/docs/images/sherylkong18.png differ diff --git a/docs/images/zbz-lvlv.png b/docs/images/zbz-lvlv.png new file mode 100644 index 00000000000..93be075f053 Binary files /dev/null and b/docs/images/zbz-lvlv.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..28f5c1abf8b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,19 +1,49 @@ --- layout: page -title: AddressBook Level-3 +title: Minefriends 👾 --- -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) -[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) +[![CI Status](https://github.com/AY2223S1-CS2103T-T10-4/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2223S1-CS2103T-T10-4/tp/actions) +[![codecov](https://codecov.io/gh/AY2223S1-CS2103T-T10-4/tp/branch/master/graph/badge.svg?token=SQQ4K5J7N0)](https://codecov.io/gh/AY2223S1-CS2103T-T10-4/tp) -![Ui](images/Ui.png) +This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). + +:bulb: Before we begin, it is important to note that: +* Minefriends is not affiliated with Minecraft, Mojang Studios or Microsoft in any way. +* Minefriends is an independently developed software. It is not a product owned by Mojang Studios or Microsoft. +* Team Minefriends do not own Minecraft, in whole or in part. + +Minefriends is a **desktop app for avid Minecraft players to manage information about their online friends**. + +With Minefriends, you can: -**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). +- Maintain a list of all your online friends +- Access your friends' information easily, such as their emails and social handles +- Keep track of which servers and game modes your friends are playing on +- Keep track of the timings in which your friends play Minecraft + +Minefriends is optimized for use via a Command Line Interface (CLI), while also having the benefits of a Graphical User Interface (GUI). + +**[Download Minefriends](https://github.com/AY2223S1-CS2103T-T10-4/tp/releases) and run it now!** + +Sample interface: +![Ui](images/Ui.png) -* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). -* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. +### Acknowledgements: +External libraries +* [ControlsFX](https://github.com/controlsfx/controlsfx) +* [TestFX](https://github.com/TestFX/TestFX) -**Acknowledgements** +Graphics and assets +* App Icon - [Minecraft Book & Quill icon](https://minecraft.fandom.com/wiki/Book_and_Quill) +* Font - [Minecraft font](https://fontmeme.com/fonts/minecraft-font/) +* Background textures - [Dirt block](https://minecraft.fandom.com/wiki/Dirt) +* Background textures - [Grass block](https://minecraft.fandom.com/wiki/Grass_Block) +* Background textures - [Bedrock](https://minecraft.fandom.com/wiki/Bedrock) +* [Creeper texture](https://en.wikipedia.org/wiki/File:Creeper_(Minecraft).png) +* [Creeper explosion texture](https://toppng.com/show_download/244692/explosion-pixel-art/large) +* Creeper explosion audio: Self-recorded in game -* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5) +These assets are copyright Mojang Studios, which are available for non-commercial use. +The terms of use can be found [here](https://www.minecraft.net/en-us/terms). diff --git a/docs/team/chustinjeng.md b/docs/team/chustinjeng.md new file mode 100644 index 00000000000..1294b212e01 --- /dev/null +++ b/docs/team/chustinjeng.md @@ -0,0 +1,57 @@ +--- +layout: page +title: Project Portfolio Page for Justin Cheng +--- + +--- +## Overview + +--- +Minefriends is an application for Minecraft multiplayer players to keep track of their Minecraft friends. It aims to help players to find the right people to play the right game mode at the right time. + +## Summary of Contributions + +--- +I was in charge with the Object-Oriented Programming part of the project, ensuring that our code achieves high levels of abstraction and is of good quality overall. Furthermore, +I was responsible with bug-testing and fixing, as well as coming up with test cases to ensure that the code is safe for usage. + +### Code contributed + +The link to my contributions in the code dashboard is [here](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=chustinjeng&breakdown=true). + +### Enhancements implemented + +- Added additional fields to the `Person` class such as Minecraft servers and available timings to improve versatility of user input +- Implemented the `suggest dt/now` feature that allows players to find friends that are online currently + - This involved using the current time shown on personal device to filter out friends, plus utilising inbuilt Java API such as LocalDateTime. +- Conducted thorough bug finding and bug fixing on functional code +- Added many JUnit test cases that address edge cases and common bugs + +### Contributions to the User Guide + +- Contributed to the overall organisation of the user guide through use of tables and boxes +- Came up with easy-to-understand examples to demonstrate how the application can be used +- Proofread to ensure that the user guide is easy to read and appropriate for target audience + +### Contributions to the Developer's Guide + +- In charge of UML diagrams of the features implemented in the application + - Some UML diagrams are the `EditCommand` class diagram, `EditCommand` sequence diagram, as well as the `suggest dt/now` sequence diagram + - These UML diagrams are meant to provide developers with a better visual representation of how different classes and objects work with one another to ensure that the application runs. +- Proofreading and styling of the guide to ensure good readability. + +### Contributions to team-based tasks + +- Minutes taking for team meetings +- Coming up with punctual, succinct code of good quality +- Regular updates of bugs and potential bugs + +### Reviewing/mentoring contributions + +- Reviewed PRs made my teammates +- Gave advice to teammates when they encounter any coding difficulty or any bugs in their code +- Shared with teammates any difficulty that I encountered during this project so that they do not make the same mistake + +### Contributions beyond the project team + +- I contribute to the CS2103T forum by answering questions that I have the answers to. diff --git a/docs/team/onepersonhere.md b/docs/team/onepersonhere.md new file mode 100644 index 00000000000..f066511a07e --- /dev/null +++ b/docs/team/onepersonhere.md @@ -0,0 +1,62 @@ +--- +layout: page +title: Project Portfolio Page for Kang Wenhan +--- + +## Overview + +Minefriends is an application for Minecraft multiplayer players to keep track of their Minecraft friends. +It aims to help players find the right people to play the right game mode at the right time. + +## Summary of Contributions + +### Team Co-Lead + +I am the team co-lead and I assist the team lead in coordinating team meetings, milestones and conjugating +important decisions about the project direction. + +### Code Quality Lead + +I am the code quality lead, and I am in charge of ensuring that the code is written in a reasonable way according to the specified coding standards in CS2103T. + +### Code contributed +The link to my contributions in the code dashboard is [here](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=onepersonhere&breakdown=true). + +### Enhancements implemented + +**Autocomplete feature**
+I spent a lot of time researching and implementing the auto-complete feature: +* took advantage of the ControlsFX library and making the necessary changes to the `CommandBox` class. +* make it look and feel similar to the Minecraft theme used in the application by modifying the css files. + +**Help Screen**
+I implemented the help screen to show the user a list of commands and their descriptions: +* `HelpWindow` class, to implement the help screen. +* `MainWindow` class, to implement the show help upon startup when the user has no data. +* `HelpWindow` css file, to make it look and feel similar to the Minecraft theme used in the application. + +### Contributions to the User Guide + +* Content Page Layout +* Autocomplete feature +* Frequently Asked Questions (FAQ) + +### Contributions to the Developer's Guide + +* Autocomplete feature + * Flow of Autocomplete feature + +### Contributions to team-based tasks + +* Set up the project repository, website and issue tracker. +* Set up the project release for v1.3. +* Ensure that everyone has automated code quality check setup on their IDE. + +### Reviewing/mentoring contributions + +* All PR review goes through either me or Bo Zheng, to be vetted before submission. +* I provide guidance to the team on how to implement certain features. +* I provide reminder to the Team Lead for the tasks that need to be done. + +### Contributions beyond the project team +* I actively contribute to the CS2103T Forum, by asking questions that some of my peers may have, and answering questions that I can answer. diff --git a/docs/team/shawnkai.md b/docs/team/shawnkai.md new file mode 100644 index 00000000000..3ec6ef0edcc --- /dev/null +++ b/docs/team/shawnkai.md @@ -0,0 +1,43 @@ +--- +layout: page +title: Project Portfolio Page for Shawn Tan +--- + +## Overview + +Minefriends is an application for Minecraft multiplayer players to keep track of their Minecraft friends. +It aims to help players find the right people to play the right game mode at the right time. + +## Summary of Contributions + +I was the code quality maintainer throughout the project. + +### Code contributed +The link to my contributions in the code dashboard is [here](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=shawnkai&breakdown=true). + +### Enhancements implemented +- Creating a new representation for the field "Minecraft Server" +- Thorough bug finding especially for failed test cases +- Bug fixing on functional code +- Thorough testing for most optional fields + +### Contributions to the User Guide +- Uploaded almost all screenshots and pictures +- Wrote the explanation for all features except Suggest feature +- Wrote the content for details of each field + +### Contributions to the Developer's Guide +* Section on Server Representation + +### Contributions to team-based tasks +* Minutes taking for team meetings +* Coming up with punctual, succinct code of good quality +* Regular updates of bugs and potential bugs +* Increase coverage of test cases +* Help to come up with user stories and listed all of them on Github + +### Reviewing/mentoring contributions +* Gave advice to teammates when they encounter any coding difficulty or any bugs in their code +* Shared with teammates any difficulty that I encountered during this project so that they do not make the same mistake + +### Contributions beyond the project team diff --git a/docs/team/sherylkong18.md b/docs/team/sherylkong18.md new file mode 100644 index 00000000000..8c5cd87b0e6 --- /dev/null +++ b/docs/team/sherylkong18.md @@ -0,0 +1,57 @@ +--- +layout: page +title: Project Portfolio Page for Sheryl Kong +--- + +## Overview + +Minefriends is an all new friend-tracking program to help Minecraft players find the right friends at the right time to play the right game with. + +## Summary of Contributions + +I was the developer and UI designer for the project. I was mainly in charge of writing functional code, and enhancing the UI. + +### Code contributed +The link to my contributions in the code dashboard is [here](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=sherylkong18&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other). + +### Enhancements implemented + +**Attributes in `People` class** +- I implemented the following identity fields in the`People` class: + - Social + - GameType + - Country + +**UI enhancement** +- I created and added the _Minefriends and Creeper_ logo to our main screen. +- I implemented the exploding-creeper animation shown when an invalid command is entered. + +### Contributions to the User Guide +- Drafting the skeleton of the UG +- Description of the identity fields (Social class, GameType class, Country class) +- Command summary page + +### Contributions to the Developer's Guide + +* Servers, GameType and Socials as Coloured Tags in Friend's profile + * Implementation structure and flow + * Class diagram of the feature within the UI system + +* Available Timings + * Implementation structure and flow + * Class diagram of the feature within the Model system + +* Formatting and styling the guide + +### Contributions to team-based tasks +- In-charge of turning in some team submissions (e.g.UG draft) +- Reminds the team about upcoming deadlines at times +- Writing neat and readable code when implementing features and enhancing the UI. + +### Reviewing/mentoring contributions +- Reviewed PRs made by my teammates. +- Provided help to team members when finding errors in code and solving bugs + +### Contributions beyond the project team +- I posted a question on the forum when I faced a problem that was Mac-related, as I foresee that this issue would be faced by other Mac users. +- I did not post any answers to the questions posted by other students on the forum, but I believe the question I posted would help others greatly. diff --git a/docs/team/zbz-lvlv.md b/docs/team/zbz-lvlv.md new file mode 100644 index 00000000000..a6f6804aa3e --- /dev/null +++ b/docs/team/zbz-lvlv.md @@ -0,0 +1,75 @@ +--- +layout: page +title: Project Portfolio Page for Zhang Bozheng +--- + +## Overview + +Minefriends is an application for Minecraft multiplayer players to keep track of their Minecraft friends. +It aims to help players find the right people to play the right game mode at the right time. + +## Summary of Contributions + +#### Team Lead +I am the team lead and the overall in charge of the project, coordinating team meetings, setting milestones and making +important decisions about the direction of the project. + +#### Head Programmer +I set the general technical direction of the project, engaging my team in discussions about how to structure our program. +I am the "last-checkpoint" before the feature goes live, ensuring that features work and that the code is written in a reasonable way. + +### Code contributed +The link to my contributions in the code dashboard is [here](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=zbz-lvlv&breakdown=true). + +### Enhancements implemented + +**Suggest a friend** + +I built a mechanism to suggest a Minecraft friend to play with based on a set of keywords and timing constraints. +* `DayTimeInWeek` class, a way to store a particular point in time +* `Keyword` class, a way to store a `Keyword` for search +* `PersonSuggestionPredicate` class, to implement to logic for `suggest`. + +**UI/UX Look-and-Feel** + +I enhanced the UI/UX to give it a stronger Minecraft-based look and feel. +* Enhancements to the css files to build the graphics system +* Design of textures and the layout + +**Minecraft username** + +I added the attribute of Minecraft username to the `Person` class. + +**Others** + +Every other enhancements done by other group members were vetted by me and minor +adjustments were made for almost all of them. + +### Contributions to the User Guide + +* Suggest command +* Add command +* Edit command +* Command summary +* Introduction +* Vetting of other member's work + +### Contributions to the Developer's Guide + +* Suggest command + * Class diagram of the implementation structure + * Sequence diagram of the command execution + +### Contributions to team-based tasks + +* Asking questions on the forum (Usage of Minecraft assets) +* Liaison with tutor and professor for the team + +### Reviewing/mentoring contributions + +* All PR review goes through either me or Wen Han, to be vetted before submission +* I provided guidance in coding and git to my teammates + +### Contributions beyond the project team + +* I posted a tip on the forum for students who could not run the iP jar diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023e..41d9927a4d4 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 4133aaa0151..a8e790b560f 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -21,7 +21,6 @@ import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; -import seedu.address.model.util.SampleDataUtil; import seedu.address.storage.AddressBookStorage; import seedu.address.storage.JsonAddressBookStorage; import seedu.address.storage.JsonUserPrefsStorage; @@ -40,6 +39,8 @@ public class MainApp extends Application { private static final Logger logger = LogsCenter.getLogger(MainApp.class); + private static boolean isNewDataFileCreated = false; + protected Ui ui; protected Logic logic; protected Storage storage; @@ -79,15 +80,18 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { try { addressBookOptional = storage.readAddressBook(); if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + logger.info("Data file not found. Will be starting with an empty AddressBook"); + isNewDataFileCreated = true; } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = addressBookOptional.orElseGet(AddressBook::new); } catch (DataConversionException e) { logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); initialData = new AddressBook(); + isNewDataFileCreated = true; } catch (IOException e) { logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); initialData = new AddressBook(); + isNewDataFileCreated = true; } return new ModelManager(initialData, userPrefs); @@ -180,4 +184,8 @@ public void stop() { logger.severe("Failed to save preferences " + StringUtil.getDetails(e)); } } + + public static boolean isNewDataFileCreated() { + return isNewDataFileCreated; + } } diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/address/commons/util/JsonUtil.java index 8ef609f055d..f14be98248e 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/address/commons/util/JsonUtil.java @@ -57,6 +57,7 @@ static T deserializeObjectFromJsonFile(Path jsonFile, Class classOfObject */ public static Optional readJsonFile( Path filePath, Class classOfObjectToDeserialize) throws DataConversionException { + requireNonNull(filePath); if (!Files.exists(filePath)) { @@ -72,7 +73,6 @@ public static Optional readJsonFile( logger.warning("Error reading from jsonFile file " + filePath + ": " + e); throw new DataConversionException(e); } - return Optional.of(jsonFile); } diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb..b05b31c408f 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -38,6 +38,19 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { .anyMatch(preppedWord::equalsIgnoreCase); } + /** + * Checks if sentence contains str, ignoring case. + * @param sentence The longer string + * @param str The keyword to check in the longer string + * @return A boolean indicating if sentence contains str ignoring case. + */ + public static boolean containsIgnoreCase(String sentence, String str) { + requireNonNull(sentence); + requireNonNull(str); + + return sentence.toLowerCase().contains(str.toLowerCase()); + } + /** * Returns a detailed message of the t, including the stack trace. */ diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 71656d7c5c8..fab89c4dd0e 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -2,10 +2,16 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COUNTRY; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GAME_TYPE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MINECRAFT_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MINECRAFT_SERVER; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SOCIAL; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME_INTERVAL; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; @@ -17,27 +23,47 @@ public class AddCommand extends Command { public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " + public static final String DESCRIPTION = "Adds a person to the address book."; + public static final String PARAMETERS = + PREFIX_NAME + "NAME " + + PREFIX_MINECRAFT_NAME + "MINECRAFT_NAME \n" + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_EMAIL + "EMAIL] " + + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_COUNTRY + "COUNTRY] " + + "[" + PREFIX_SOCIAL + "SOCIAL]* " + + "[" + PREFIX_MINECRAFT_SERVER + "SERVER]* " + + "[" + PREFIX_TAG + "TAG]* " + + "[" + PREFIX_GAME_TYPE + "GAME_TYPE]* " + + "[" + PREFIX_TIME_INTERVAL + "TIME_INTERVAL]*"; + public static final String EXAMPLES = + COMMAND_WORD + " " + PREFIX_NAME + "John Doe " + + PREFIX_MINECRAFT_NAME + "john_doe_12345 " + PREFIX_PHONE + "98765432 " + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_ADDRESS + "10 Downing Street " + + PREFIX_COUNTRY + "Australia " + + PREFIX_SOCIAL + "fb@Bozheng " + + PREFIX_SOCIAL + "ig@catherine_33334 " + + PREFIX_MINECRAFT_SERVER + "catherine_server@192.168.1.255 " + + PREFIX_MINECRAFT_SERVER + "hypixel@hypixel.com " + PREFIX_TAG + "friends " + PREFIX_TAG + "owesMoney"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": " + DESCRIPTION + "\n\n" + + "Parameters: \n" + + PARAMETERS + "\n\n" + + "Example: \n" + EXAMPLES; public static final String MESSAGE_SUCCESS = "New person added: %1$s"; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; private final Person toAdd; + public AddCommand() { + this.toAdd = null; + } + /** * Creates an AddCommand to add the specified {@code Person} */ @@ -58,6 +84,21 @@ public CommandResult execute(Model model) throws CommandException { return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); } + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public String getParameters() { + return PARAMETERS; + } + + @Override + public String getExamples() { + return EXAMPLES; + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..04f9dcda679 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -12,7 +12,9 @@ public class ClearCommand extends Command { public static final String COMMAND_WORD = "clear"; public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - + public static final String DESCRIPTION = "Clears the address book."; + public static final String PARAMETERS = ""; + public static final String EXAMPLES = COMMAND_WORD; @Override public CommandResult execute(Model model) { @@ -20,4 +22,19 @@ public CommandResult execute(Model model) { model.setAddressBook(new AddressBook()); return new CommandResult(MESSAGE_SUCCESS); } + + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public String getParameters() { + return PARAMETERS; + } + + @Override + public String getExamples() { + return EXAMPLES; + } } diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 64f18992160..9074d451e7f 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -16,5 +16,7 @@ public abstract class Command { * @throws CommandException If an error occurs during command execution. */ public abstract CommandResult execute(Model model) throws CommandException; - + public abstract String getDescription(); + public abstract String getParameters(); + public abstract String getExamples(); } diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 02fd256acba..1b7ec7f3ba3 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -17,15 +17,24 @@ public class DeleteCommand extends Command { public static final String COMMAND_WORD = "delete"; + public static final String DESCRIPTION = + "Deletes a person identified by the index number used in the displayed person list."; + public static final String PARAMETERS = "INDEX (must be a positive integer)"; + public static final String EXAMPLES = COMMAND_WORD + " 1"; + public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; + + ": " + DESCRIPTION + "\n" + + "Parameters: " + PARAMETERS + "\n" + + "Example: " + EXAMPLES; public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; private final Index targetIndex; + public DeleteCommand() { + this.targetIndex = null; + } + public DeleteCommand(Index targetIndex) { this.targetIndex = targetIndex; } @@ -44,6 +53,21 @@ public CommandResult execute(Model model) throws CommandException { return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); } + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public String getParameters() { + return PARAMETERS; + } + + @Override + public String getExamples() { + return EXAMPLES; + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 7e36114902f..0025fe45674 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -2,10 +2,16 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COUNTRY; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GAME_TYPE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MINECRAFT_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MINECRAFT_SERVER; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SOCIAL; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME_INTERVAL; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import java.util.Collections; @@ -20,11 +26,17 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; import seedu.address.model.person.Address; +import seedu.address.model.person.Country; import seedu.address.model.person.Email; +import seedu.address.model.person.GameType; +import seedu.address.model.person.ITimesAvailable; +import seedu.address.model.person.MinecraftName; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Server; +import seedu.address.model.person.Social; +import seedu.address.model.person.Tag; /** * Edits the details of an existing person in the address book. @@ -32,21 +44,32 @@ public class EditCommand extends Command { public static final String COMMAND_WORD = "edit"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " + public static final String DESCRIPTION = "Edits the details of the person identified " + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " + + "Existing values will be overwritten by the input values."; + public static final String PARAMETERS = "INDEX (must be a positive integer) " + "[" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " + + "[" + PREFIX_COUNTRY + "COUNTRY] " + + "[" + PREFIX_MINECRAFT_NAME + "MINECRAFT_NAME] " + + "[" + PREFIX_MINECRAFT_SERVER + "MINECRAFT_SERVER]* " + + "[" + PREFIX_GAME_TYPE + "GAME_TYPE]* " + + "[" + PREFIX_SOCIAL + "SOCIAL]* " + + "[" + PREFIX_TAG + "TAG]* " + + "[" + PREFIX_TIME_INTERVAL + "TIME_INTERVAL]*"; + public static final String EXAMPLES = COMMAND_WORD + " 1 " + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; + + PREFIX_EMAIL + "johndoe@example.com " + + PREFIX_SOCIAL + "fb@John Doe "; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": " + DESCRIPTION + "\n\n" + + "Parameters: \n" + + PARAMETERS + "\n" + + "Example: " + EXAMPLES; - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; + public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited friend: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; @@ -54,6 +77,16 @@ public class EditCommand extends Command { private final EditPersonDescriptor editPersonDescriptor; /** + * An empty constructor for EditCommand. + */ + public EditCommand() { + this.index = null; + this.editPersonDescriptor = null; + } + + /** + * Constructs a {@code EditCommand}. + * * @param index of the person in the filtered person list to edit * @param editPersonDescriptor details to edit the person with */ @@ -86,6 +119,21 @@ public CommandResult execute(Model model) throws CommandException { return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); } + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public String getParameters() { + return PARAMETERS; + } + + @Override + public String getExamples() { + return EXAMPLES; + } + /** * Creates and returns a {@code Person} with the details of {@code personToEdit} * edited with {@code editPersonDescriptor}. @@ -94,12 +142,21 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript assert personToEdit != null; Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); + MinecraftName updatedMinecraftName = editPersonDescriptor.getMinecraftName() + .orElse(personToEdit.getMinecraftName()); Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); + Set updatedSocials = editPersonDescriptor.getSocials().orElse(personToEdit.getSocials()); Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + Set updatedServers = editPersonDescriptor.getServers().orElse(personToEdit.getServers()); + Country updatedCountry = editPersonDescriptor.getCountry().orElse(personToEdit.getCountry()); + Set updatedGameType = editPersonDescriptor.getGameTypes().orElse(personToEdit.getGameType()); + Set updatedTimeIntervals = editPersonDescriptor.getTimeIntervals() + .orElse(personToEdit.getTimesAvailable()); + + return new Person(updatedName, updatedMinecraftName, updatedPhone, updatedEmail, updatedAddress, + updatedSocials, updatedTags, updatedServers, updatedCountry, updatedGameType, updatedTimeIntervals); } @Override @@ -125,11 +182,18 @@ public boolean equals(Object other) { * corresponding field value of the person. */ public static class EditPersonDescriptor { + private Name name; + private MinecraftName minecraftName; private Phone phone; private Email email; private Address address; + private Set socials; private Set tags; + private Set servers; + private Country country; + private Set gameTypes; + private Set timeIntervals; public EditPersonDescriptor() {} @@ -139,17 +203,24 @@ public EditPersonDescriptor() {} */ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setName(toCopy.name); + setMinecraftName(toCopy.minecraftName); setPhone(toCopy.phone); setEmail(toCopy.email); setAddress(toCopy.address); + setSocials(toCopy.socials); setTags(toCopy.tags); + setServers(toCopy.servers); + setCountry(toCopy.country); + setGameTypes(toCopy.gameTypes); + setTimeIntervals(toCopy.timeIntervals); } /** * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, minecraftName, phone, email, address, + socials, tags, servers, country, gameTypes, timeIntervals); } public void setName(Name name) { @@ -160,6 +231,14 @@ public Optional getName() { return Optional.ofNullable(name); } + public void setMinecraftName(MinecraftName name) { + this.minecraftName = name; + } + + public Optional getMinecraftName() { + return Optional.ofNullable(minecraftName); + } + public void setPhone(Phone phone) { this.phone = phone; } @@ -184,6 +263,25 @@ public Optional
getAddress() { return Optional.ofNullable(address); } + /** + * Sets {@code socials} to this object's {@code socials}. + * A defensive copy of {@code socials} is used internally. + */ + public void setSocials(Set socials) { + this.socials = (socials != null) ? new HashSet<>(socials) : null; + } + + /** + * Returns an unmodifiable socials set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code socials} is null. + */ + public Optional> getSocials() { + return (socials != null) + ? Optional.of(Collections.unmodifiableSet(socials)) + : Optional.empty(); + } + /** * Sets {@code tags} to this object's {@code tags}. * A defensive copy of {@code tags} is used internally. @@ -201,8 +299,41 @@ public Optional> getTags() { return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); } + public void setServers(Set servers) { + this.servers = (servers != null) ? new HashSet<>(servers) : null; + } + + public Optional> getServers() { + return (servers != null) ? Optional.of(Collections.unmodifiableSet(servers)) : Optional.empty(); + } + + public void setCountry(Country country) { + this.country = country; + } + + public Optional getCountry() { + return Optional.ofNullable(country); + } + + public void setGameTypes(Set gameTypes) { + this.gameTypes = (gameTypes != null) ? new HashSet<>(gameTypes) : null; + } + + public Optional> getGameTypes() { + return (gameTypes != null) ? Optional.of(Collections.unmodifiableSet(gameTypes)) : Optional.empty(); + } + + public void setTimeIntervals(Set timeIntervals) { + this.timeIntervals = (timeIntervals != null) ? new HashSet<>(timeIntervals) : null; + } + + public Optional> getTimeIntervals() { + return (timeIntervals != null) ? Optional.of(Collections.unmodifiableSet(timeIntervals)) : Optional.empty(); + } + @Override public boolean equals(Object other) { + // short circuit if same object if (other == this) { return true; @@ -217,10 +348,16 @@ public boolean equals(Object other) { EditPersonDescriptor e = (EditPersonDescriptor) other; return getName().equals(e.getName()) + && getMinecraftName().equals(e.getMinecraftName()) && getPhone().equals(e.getPhone()) && getEmail().equals(e.getEmail()) && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); + && getSocials().equals(e.getSocials()) + && getTags().equals(e.getTags()) + && getServers().equals(e.getServers()) + && getCountry().equals(e.getCountry()) + && getGameTypes().equals(e.getGameTypes()) + && getTimeIntervals().equals(e.getTimeIntervals()); } } } diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..1bc5c923d33 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -8,6 +8,9 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; + public static final String DESCRIPTION = "Exits the program."; + public static final String PARAMETERS = ""; + public static final String EXAMPLES = COMMAND_WORD; public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; @@ -16,4 +19,19 @@ public CommandResult execute(Model model) { return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); } + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public String getParameters() { + return PARAMETERS; + } + + @Override + public String getExamples() { + return EXAMPLES; + } + } diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index d6b19b0a0de..c6c4c7d5053 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -8,19 +8,35 @@ /** * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. + * Keyword matching is case-insensitive. */ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; + public static final String DESCRIPTION = "Finds all persons whose names contain any of " + + "the specified keywords (case-insensitive) and displays them as a list with index numbers."; + public static final String PARAMETERS = "KEYWORD [MORE_KEYWORDS]..."; + public static final String EXAMPLES = COMMAND_WORD + " alice bob charlie"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": " + DESCRIPTION + "\n" + + PARAMETERS + "\n" + + "Example: " + EXAMPLES; private final NameContainsKeywordsPredicate predicate; + /** + * Default constructor for {@code FindCommand}. + */ + public FindCommand() { + this.predicate = null; + } + + /** + * Constructs a {@code FindCommand}. + * + * @param predicate The NameContainsKeywordsPredicate that is used for + * FindCommand. + */ public FindCommand(NameContainsKeywordsPredicate predicate) { this.predicate = predicate; } @@ -33,6 +49,21 @@ public CommandResult execute(Model model) { String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); } + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public String getParameters() { + return PARAMETERS; + } + + @Override + public String getExamples() { + return EXAMPLES; + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..a47362cfb27 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -8,6 +8,9 @@ public class HelpCommand extends Command { public static final String COMMAND_WORD = "help"; + public static final String DESCRIPTION = "Shows program usage instructions."; + public static final String PARAMETERS = ""; + public static final String EXAMPLES = COMMAND_WORD; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" + "Example: " + COMMAND_WORD; @@ -18,4 +21,19 @@ public class HelpCommand extends Command { public CommandResult execute(Model model) { return new CommandResult(SHOWING_HELP_MESSAGE, true, false); } + + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public String getParameters() { + return PARAMETERS; + } + + @Override + public String getExamples() { + return EXAMPLES; + } } diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..746329dce9d 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -11,14 +11,31 @@ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; + public static final String DESCRIPTION = "Lists all persons in the address book."; + public static final String PARAMETERS = ""; + public static final String EXAMPLES = COMMAND_WORD; public static final String MESSAGE_SUCCESS = "Listed all persons"; - @Override public CommandResult execute(Model model) { requireNonNull(model); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); return new CommandResult(MESSAGE_SUCCESS); } + + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public String getParameters() { + return PARAMETERS; + } + + @Override + public String getExamples() { + return EXAMPLES; + } } diff --git a/src/main/java/seedu/address/logic/commands/SuggestCommand.java b/src/main/java/seedu/address/logic/commands/SuggestCommand.java new file mode 100644 index 00000000000..029ff495d04 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SuggestCommand.java @@ -0,0 +1,93 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY_TIME_OF_WEEK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_KEYWORD; + +import seedu.address.commons.core.Messages; +import seedu.address.model.Model; +import seedu.address.model.person.PersonSuggestionPredicate; + +/** + * Suggests a list of friends to play Minecraft with based on a set of constraints. + * Keyword matching is done through "string contains" and is not case-sensitive. + */ +public class SuggestCommand extends Command { + + public static final String COMMAND_WORD = "suggest"; + public static final String DESCRIPTION = "Suggests a list of friends based on a given set of constraints.\n\n" + + "Provide some DayTimeInWeek (eg. mon@1930) " + + "and they will be matched against the availability periods of the player. " + + "You can type \"now\" to find friends that are online currently.\n\n" + + "Provide keywords, and they will be matched against all attributes " + + "(Minecraft username, social handles etc.) by checking if the attributes " + + "contain the given keywords. All keywords must be matched."; + public static final String PARAMETERS = + "[" + PREFIX_DAY_TIME_OF_WEEK + "DAY TIME OF WEEK] " + + "[" + PREFIX_KEYWORD + "KEYWORD]\n" + + "All day time of week must come before all keywords"; + public static final String EXAMPLES = COMMAND_WORD + " " + + PREFIX_DAY_TIME_OF_WEEK + "mon@1755 " + + PREFIX_DAY_TIME_OF_WEEK + "fri@2355 " + + PREFIX_DAY_TIME_OF_WEEK + "now " + + PREFIX_KEYWORD + "sally tan " + PREFIX_KEYWORD + "sally.245\n" + + COMMAND_WORD + " " + + PREFIX_KEYWORD + "91234567 " + + PREFIX_KEYWORD + "sally.tan.454r33 " + + PREFIX_KEYWORD + "sally.tan.454@gmail.com"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": " + DESCRIPTION + "\n\n" + + "Parameters:\n" + + PARAMETERS + "\n\n" + + "Example: " + EXAMPLES; + + private final PersonSuggestionPredicate predicate; + + /** + * Default constructor for {@code SuggestCommand}. + */ + public SuggestCommand() { + this.predicate = null; + } + + /** + * Constructs a {@code SuggestCommand}. + * + * @param predicate The PersonSuggestionPredicate for the SuggestCommand + * to run on. + */ + public SuggestCommand(PersonSuggestionPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + } + + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public String getParameters() { + return PARAMETERS; + } + + @Override + public String getExamples() { + return EXAMPLES; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SuggestCommand // instanceof handles nulls + && predicate.equals(((SuggestCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3b8bfa035e8..b22b49dca78 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -2,22 +2,35 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COUNTRY; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GAME_TYPE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MINECRAFT_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MINECRAFT_SERVER; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SOCIAL; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME_INTERVAL; import java.util.Set; import java.util.stream.Stream; import seedu.address.logic.commands.AddCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.parser.utils.CheckedFunction; import seedu.address.model.person.Address; +import seedu.address.model.person.Country; import seedu.address.model.person.Email; +import seedu.address.model.person.GameType; +import seedu.address.model.person.ITimesAvailable; +import seedu.address.model.person.MinecraftName; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Server; +import seedu.address.model.person.Social; +import seedu.address.model.person.Tag; /** * Parses input arguments and creates a new AddCommand object @@ -30,25 +43,50 @@ public class AddCommandParser implements Parser { * @throws ParseException if the user input does not conform the expected format */ public AddCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_MINECRAFT_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_SOCIAL, PREFIX_TAG, PREFIX_MINECRAFT_SERVER, PREFIX_COUNTRY, + PREFIX_GAME_TYPE, PREFIX_TIME_INTERVAL); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_MINECRAFT_NAME) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); + Name name = (Name) parseMandatoryArgument(PREFIX_NAME, argMultimap, ParserUtil::parseName); + MinecraftName mcName = (MinecraftName) parseMandatoryArgument(PREFIX_MINECRAFT_NAME, argMultimap, + ParserUtil::parseMinecraftName); + Phone phone = (Phone) parseOptionalArgument(PREFIX_PHONE, argMultimap, ParserUtil::parsePhone); + Email email = (Email) parseOptionalArgument(PREFIX_EMAIL, argMultimap, ParserUtil::parseEmail); + Address address = (Address) parseOptionalArgument(PREFIX_ADDRESS, argMultimap, ParserUtil::parseAddress); + Country country = (Country) parseOptionalArgument(PREFIX_COUNTRY, argMultimap, ParserUtil::parseCountry); + Set socialList = ParserUtil.parseSocials(argMultimap.getAllValues(PREFIX_SOCIAL)); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + Set serverList = ParserUtil.parseServers(argMultimap.getAllValues(PREFIX_MINECRAFT_SERVER)); + Set gameTypeList = ParserUtil.parseGameTypes(argMultimap.getAllValues(PREFIX_GAME_TYPE)); + Set timesAvailable = ParserUtil.parseTimeIntervals(argMultimap + .getAllValues(PREFIX_TIME_INTERVAL)); - Person person = new Person(name, phone, email, address, tagList); + Person person = new Person(name, mcName, phone, email, address, socialList, tagList, serverList, country, + gameTypeList, timesAvailable); return new AddCommand(person); } + private Object parseOptionalArgument(Prefix prefix, ArgumentMultimap argMultimap, + CheckedFunction parserFn) throws ParseException { + if (argMultimap.getValue(prefix).isPresent()) { + return parserFn.apply(argMultimap.getValue(prefix).get()); + } + return null; + } + + private Object parseMandatoryArgument(Prefix prefix, ArgumentMultimap argMultimap, + CheckedFunction parserFn) throws ParseException { + return parserFn.apply(argMultimap.getValue(prefix).get()); + } + /** * Returns true if none of the prefixes contains empty {@code Optional} values in the given * {@code ArgumentMultimap}. diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 1e466792b46..4e1910844ed 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -15,6 +15,7 @@ import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.SuggestCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -59,6 +60,9 @@ public Command parseCommand(String userInput) throws ParseException { case FindCommand.COMMAND_WORD: return new FindCommandParser().parse(arguments); + case SuggestCommand.COMMAND_WORD: + return new SuggestCommandParser().parse(arguments); + case ListCommand.COMMAND_WORD: return new ListCommand(); diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..86ba20892b1 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -7,9 +7,17 @@ public class CliSyntax { /* Prefix definitions */ public static final Prefix PREFIX_NAME = new Prefix("n/"); + public static final Prefix PREFIX_MINECRAFT_NAME = new Prefix("m/"); public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_SOCIAL = new Prefix("s/"); + public static final Prefix PREFIX_MINECRAFT_SERVER = new Prefix("ms/"); + public static final Prefix PREFIX_COUNTRY = new Prefix("c/"); + public static final Prefix PREFIX_GAME_TYPE = new Prefix("gt/"); + public static final Prefix PREFIX_DAY_TIME_OF_WEEK = new Prefix("dt/"); + public static final Prefix PREFIX_KEYWORD = new Prefix("k/"); + public static final Prefix PREFIX_TIME_INTERVAL = new Prefix("ti/"); } diff --git a/src/main/java/seedu/address/logic/parser/CurrentTimeConverter.java b/src/main/java/seedu/address/logic/parser/CurrentTimeConverter.java new file mode 100644 index 00000000000..acd58d19f81 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/CurrentTimeConverter.java @@ -0,0 +1,60 @@ +package seedu.address.logic.parser; + +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Converts the current time to a String format. + * Makes use of Java time api to obtain current time. + */ +public class CurrentTimeConverter { + + /** + * The standard format to convert Date and Time to. + */ + public static final DateTimeFormatter STANDARD_TIME_FORMAT = DateTimeFormatter.ofPattern("HHmm"); + + /** + * Converts current time to String representation. + * Current time is dependent on the time displayed on + * your personal device. + * @return A String representation of current time. + */ + public static String findTimeNowInString() { + LocalDateTime currentTime = LocalDateTime.now(); + DayOfWeek currentDay = currentTime.getDayOfWeek(); + String currentDayInString = convertDayOfWeekToString(currentDay); + String formattedTime = currentTime.format(STANDARD_TIME_FORMAT); + return currentDayInString + "@" + formattedTime; + } + + /** + * Converts current day to String representation. + * Current time is dependent on the time displayed on + * your personal device. + * @param dayOfWeek The day of the week. + * @return String representation of current day. + */ + public static String convertDayOfWeekToString(DayOfWeek dayOfWeek) { + switch (dayOfWeek) { + case MONDAY: + return "mon"; + case TUESDAY: + return "tue"; + case WEDNESDAY: + return "wed"; + case THURSDAY: + return "thu"; + case FRIDAY: + return "fri"; + case SATURDAY: + return "sat"; + case SUNDAY: + return "sun"; + default: + assert false; + return "error"; + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 845644b7dea..4068d04576a 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -3,10 +3,16 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_COUNTRY; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GAME_TYPE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MINECRAFT_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MINECRAFT_SERVER; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SOCIAL; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TIME_INTERVAL; import java.util.Collection; import java.util.Collections; @@ -17,7 +23,11 @@ import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.GameType; +import seedu.address.model.person.ITimesAvailable; +import seedu.address.model.person.Server; +import seedu.address.model.person.Social; +import seedu.address.model.person.Tag; /** * Parses input arguments and creates a new EditCommand object @@ -30,10 +40,13 @@ public class EditCommandParser implements Parser { * @throws ParseException if the user input does not conform the expected format */ public EditCommand parse(String args) throws ParseException { + requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_MINECRAFT_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_SOCIAL, PREFIX_TAG, PREFIX_MINECRAFT_SERVER, PREFIX_COUNTRY, + PREFIX_GAME_TYPE, PREFIX_TIME_INTERVAL); Index index; try { @@ -41,11 +54,18 @@ public EditCommand parse(String args) throws ParseException { } catch (ParseException pe) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); } - + assert index.getZeroBased() >= 0 : "index should be more than or equal to 0"; EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); } + if (argMultimap.getValue(PREFIX_MINECRAFT_NAME).isPresent()) { + editPersonDescriptor.setMinecraftName(ParserUtil.parseMinecraftName( + argMultimap.getValue(PREFIX_MINECRAFT_NAME).get() + ) + ); + } if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); } @@ -55,7 +75,20 @@ public EditCommand parse(String args) throws ParseException { if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + if (argMultimap.getValue(PREFIX_COUNTRY).isPresent()) { + editPersonDescriptor.setCountry(ParserUtil.parseCountry(argMultimap.getValue(PREFIX_COUNTRY).get())); + } + + parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)) + .ifPresent(editPersonDescriptor::setTags); + parseSocialsForEdit(argMultimap.getAllValues(PREFIX_SOCIAL)) + .ifPresent(editPersonDescriptor::setSocials); + parseMinecraftServersForEdit(argMultimap.getAllValues(PREFIX_MINECRAFT_SERVER)) + .ifPresent(editPersonDescriptor::setServers); + parseGameTypesForEdit(argMultimap.getAllValues(PREFIX_GAME_TYPE)) + .ifPresent(editPersonDescriptor::setGameTypes); + parseTimeIntervalsForEdit(argMultimap.getAllValues(PREFIX_TIME_INTERVAL)) + .ifPresent(editPersonDescriptor::setTimeIntervals); if (!editPersonDescriptor.isAnyFieldEdited()) { throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); @@ -79,4 +112,67 @@ private Optional> parseTagsForEdit(Collection tags) throws Pars return Optional.of(ParserUtil.parseTags(tagSet)); } + /** + * Parses {@code Collection socials} into a {@code Set} if {@code socials} is non-empty. + * If {@code socials} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero socials. + */ + private Optional> parseSocialsForEdit(Collection socials) throws ParseException { + assert socials != null; + + if (socials.isEmpty()) { + return Optional.empty(); + } + Collection socialSet = socials.size() == 1 && socials.contains("") ? Collections.emptySet() : socials; + return Optional.of(ParserUtil.parseSocials(socialSet)); + } + + /** + * Parses {@code Collection servers} into a {@code Set} if {@code servers} is non-empty. + * If {@code servers} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero servers. + */ + private Optional> parseMinecraftServersForEdit(Collection servers) throws ParseException { + assert servers != null; + + if (servers.isEmpty()) { + return Optional.empty(); + } + Collection serverSet = servers.size() == 1 && servers.contains("") ? Collections.emptySet() : servers; + return Optional.of(ParserUtil.parseServers(serverSet)); + } + + /** + * Parses {@code Collection gameTypes} into a {@code Set} if {@code gameTypes} is non-empty. + * If {@code gameTypes} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero gameTypes. + */ + private Optional> parseGameTypesForEdit(Collection gameTypes) throws ParseException { + assert gameTypes != null; + + if (gameTypes.isEmpty()) { + return Optional.empty(); + } + Collection gameTypesSet = + gameTypes.size() == 1 && gameTypes.contains("") ? Collections.emptySet() : gameTypes; + return Optional.of(ParserUtil.parseGameTypes(gameTypesSet)); + } + + /** + * Parses {@code Collection timeIntervals} into a {@code Set} + * if {@code timeIntervals} is non-empty. + * If {@code timeIntervals} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero ITimesAvailable. + */ + private Optional> parseTimeIntervalsForEdit(Collection timeIntervals) + throws ParseException { + assert timeIntervals != null; + + if (timeIntervals.isEmpty()) { + return Optional.empty(); + } + Collection timeIntervalSet = + timeIntervals.size() == 1 && timeIntervals.contains("") ? Collections.emptySet() : timeIntervals; + return Optional.of(ParserUtil.parseTimeIntervals(timeIntervalSet)); + } } diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..6f14d130224 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -10,10 +10,19 @@ import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.Address; +import seedu.address.model.person.Country; +import seedu.address.model.person.DayTimeInWeek; import seedu.address.model.person.Email; +import seedu.address.model.person.GameType; +import seedu.address.model.person.ITimesAvailable; +import seedu.address.model.person.Keyword; +import seedu.address.model.person.MinecraftName; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Server; +import seedu.address.model.person.Social; +import seedu.address.model.person.Tag; +import seedu.address.model.person.TimeInterval; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -50,6 +59,21 @@ public static Name parseName(String name) throws ParseException { return new Name(trimmedName); } + /** + * Parses a {@code String minecraftName} into a {@code MinecraftName}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code minecraftName} is invalid. + */ + public static MinecraftName parseMinecraftName(String minecraftName) throws ParseException { + requireNonNull(minecraftName); + String trimmedName = minecraftName.trim(); + if (!MinecraftName.isValidMinecraftName(trimmedName)) { + throw new ParseException(MinecraftName.MESSAGE_CONSTRAINTS); + } + return new MinecraftName(trimmedName); + } + /** * Parses a {@code String phone} into a {@code Phone}. * Leading and trailing whitespaces will be trimmed. @@ -95,6 +119,110 @@ public static Email parseEmail(String email) throws ParseException { return new Email(trimmedEmail); } + /** + * Parses a {@code Collection dayTimesInWeek} into a {@code Set}. + * @param dayTimesInWeek The collection of the days of week. + * @return The set of days of week. + * @throws ParseException if the given {@code daysOfWeek} is invalid. + */ + public static Set parseDayTimesInWeek(Collection dayTimesInWeek) throws ParseException { + requireNonNull(dayTimesInWeek); + final Set set = new HashSet<>(); + for (String str : dayTimesInWeek) { + set.add(parseDayTimeInWeek(str)); + } + return set; + } + + /** + * Parses a {@code String dayTimeInWeek} into an {@code DayTimeInWeek}. + * Leading and trailing whitespaces will be trimmed. + * @param dayTimeInWeek A String representation of a day and time in a week. + * @return A DayTimeInWeek object. + * @throws ParseException if the given {@code dayTimeInWeek} is invalid. + */ + public static DayTimeInWeek parseDayTimeInWeek(String dayTimeInWeek) throws ParseException { + requireNonNull(dayTimeInWeek); + String trimmed = dayTimeInWeek.trim(); + if (DayTimeInWeek.isFindTimeNow(trimmed)) { + trimmed = CurrentTimeConverter.findTimeNowInString(); + } + if (!DayTimeInWeek.isValidDayTimeInWeekRegex(trimmed)) { + throw new ParseException(DayTimeInWeek.MESSAGE_CONSTRAINTS); + } + if (!DayTimeInWeek.isValidDayTimeInWeekParsing(trimmed)) { + throw new ParseException(DayTimeInWeek.ILLEGAL_TIME_CONSTRAINTS); + } + return new DayTimeInWeek(trimmed); + } + + /** + * Parses a {@code String socialStrs} into a set of {@code Social}. + * @param socialStrs All the Strings for the socials. + * @return A set of social media handles + * @throws ParseException if the given {@code socialStr} is invalid. + */ + public static Set parseSocials(Collection socialStrs) throws ParseException { + requireNonNull(socialStrs); + final Set socialSet = new HashSet<>(); + for (String str : socialStrs) { + socialSet.add(parseSocial(str)); + } + return socialSet; + } + + /** + * Parses a {@code String socialStr} into a {@code Social}. + * + * @param socialStr the social media platform string to be parsed + * @return The social media platform + * @throws ParseException if the given {@code socialStr} is invalid + */ + public static Social parseSocial(String socialStr) throws ParseException { + requireNonNull(socialStr); + String trimmedSocial = socialStr.trim(); + if (!Social.isValidSocial(trimmedSocial)) { + throw new ParseException(Social.MESSAGE_CONSTRAINTS); + } + String[] strArray = trimmedSocial.split("@"); + String platform = strArray[0]; + String handle = strArray[1]; + return new Social(handle, platform); + } + + /** + * Parses a {@code String keywordStrs} into a set of {@code Keyword}. + * @param keywordStrs All the Strings for the keywords. + * @return A set of keywords. + * @throws ParseException if the given {@code keywordStrs} is invalid. + */ + public static Set parseKeywords(Collection keywordStrs) throws ParseException { + requireNonNull(keywordStrs); + final Set keywordSet = new HashSet<>(); + for (String str : keywordStrs) { + keywordSet.add(parseKeyword(str)); + } + return keywordSet; + } + + /** + * Parses a {@code String keywordStr} into a {@code Keyword}. + * + * @param keywordStr the social media platform to be parsed + * @return The keyword + * @throws ParseException if the given {@code keywordStr} is invalid + */ + public static Keyword parseKeyword(String keywordStr) throws ParseException { + requireNonNull(keywordStr); + + if (!Keyword.isValidKeyword(keywordStr)) { + throw new ParseException(Keyword.MESSAGE_CONSTRAINTS); + } + + return new Keyword(keywordStr); + + } + /** * Parses a {@code String tag} into a {@code Tag}. * Leading and trailing whitespaces will be trimmed. @@ -121,4 +249,123 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + /** + * Parses a {@code String server} into a {@code Server}. + * + * @param server the {@code server} string to be parsed + * @return the {@code Server} object + * @throws ParseException if the given {@code server} string is invalid + */ + public static Server parseServer(String server) throws ParseException { + requireNonNull(server); + String trimmedServer = server.trim(); + if (!Server.isValidServerName(trimmedServer)) { + throw new ParseException(Server.getServerConstraints()); + } + return new Server(trimmedServer); + } + + /** + * Parses {@code Collection servers} into a {@code Set}. + * + * @param servers the collection of servers to be parsed + * @return a set of servers + * @throws ParseException if any of the servers are invalid + */ + public static Set parseServers(Collection servers) throws ParseException { + requireNonNull(servers); + final Set serverSet = new HashSet<>(); + for (String serverName: servers) { + serverSet.add(parseServer(serverName)); + } + return serverSet; + } + + /** + * Parses a {@code String Country} into a {@code Country object}. + * @param country the country + * @return the parsed Country + * @throws ParseException if the given {@code country} is invalid. + */ + public static Country parseCountry(String country) throws ParseException { + requireNonNull(country); + String trimmedCountry = country.trim(); + if (!Country.isValidCountry(trimmedCountry)) { + throw new ParseException(Country.getMessageConstraints()); + } + return new Country(trimmedCountry); + } + + /** + * Parses a {@code String game type} into a {@code GameType object}. + * @param gameType the preferred game type. + * @return the parsed game type. + * @throws ParseException if the given {@code gameType} is invalid. + */ + + public static GameType parseGameType(String gameType) throws ParseException { + requireNonNull(gameType); + String trimmedGameType = gameType.trim(); + if (!GameType.isValidGameType(trimmedGameType)) { + throw new ParseException(GameType.getMessageConstraints()); + } + return new GameType(trimmedGameType); + } + + /** + * Parses {@code Collection game types} into a {@code Set}. + * + * @param gameTypes Collection of game types to be parsed. + * @return Set of game types. + * @throws ParseException if any of the game types are invalid. + */ + + public static Set parseGameTypes(Collection gameTypes) throws ParseException { + requireNonNull(gameTypes); + final Set gameTypeSet = new HashSet<>(); + for (String gameType : gameTypes) { + gameTypeSet.add(parseGameType(gameType)); + } + return gameTypeSet; + } + + /** + * Parses a {@code String time interval} into a {@code ITimesAvailable object}. + * @param timeInterval the time interval where the Person is online. + * @return the parsed time interval. + * @throws ParseException if the given {@code timeInterval} is invalid. + */ + public static ITimesAvailable parseTimeInterval(String timeInterval) throws ParseException { + requireNonNull(timeInterval); + String trimmedTimeInterval = timeInterval.trim(); + if (!TimeInterval.isValidTimeInterval(trimmedTimeInterval)) { + throw new ParseException(TimeInterval.getTimeIntervalConstraints()); + } + String startTime = TimeInterval.getStartingDayTimeInWeek(trimmedTimeInterval); + if (!DayTimeInWeek.isValidDayTimeInWeekParsing(startTime)) { + throw new ParseException(DayTimeInWeek.ILLEGAL_TIME_CONSTRAINTS); + } + String endTime = TimeInterval.getEndingDayTimeInWeek(trimmedTimeInterval); + if (!DayTimeInWeek.isValidDayTimeInWeekParsing(endTime)) { + throw new ParseException(DayTimeInWeek.ILLEGAL_TIME_CONSTRAINTS); + } + return new TimeInterval(trimmedTimeInterval); + } + + /** + * Parses {@code Collection time interval} into a {@code Set}. + * + * @param timeIntervals Collection of time intervals to be parsed. + * @return Set of time intervals. + * @throws ParseException if any of the time intervals are invalid. + */ + public static Set parseTimeIntervals(Collection timeIntervals) throws ParseException { + requireNonNull(timeIntervals); + final Set timeIntervalSet = new HashSet<>(); + for (String timeInterval: timeIntervals) { + timeIntervalSet.add(parseTimeInterval(timeInterval)); + } + return timeIntervalSet; + } } diff --git a/src/main/java/seedu/address/logic/parser/SuggestCommandParser.java b/src/main/java/seedu/address/logic/parser/SuggestCommandParser.java new file mode 100644 index 00000000000..c79720eea10 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SuggestCommandParser.java @@ -0,0 +1,55 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DAY_TIME_OF_WEEK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_KEYWORD; + +import java.util.Collection; +import java.util.Set; + +import seedu.address.logic.commands.SuggestCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.DayTimeInWeek; +import seedu.address.model.person.Keyword; +import seedu.address.model.person.PersonSuggestionPredicate; + +/** + * Parses input arguments and creates a new SuggestCommand object + */ +public class SuggestCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindCommand + * and returns a FindCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SuggestCommand parse(String args) throws ParseException { + + requireNonNull(args); + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_DAY_TIME_OF_WEEK, PREFIX_KEYWORD); + + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SuggestCommand.MESSAGE_USAGE)); + } + + Collection cs = argMultimap.getAllValues(PREFIX_DAY_TIME_OF_WEEK); + Set dayTimesInWeek = ParserUtil.parseDayTimesInWeek( + argMultimap.getAllValues(PREFIX_DAY_TIME_OF_WEEK)); + Set keywords = ParserUtil.parseKeywords(argMultimap.getAllValues(PREFIX_KEYWORD)); + + if ((dayTimesInWeek.isEmpty() && keywords.isEmpty()) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SuggestCommand.MESSAGE_USAGE)); + } + + return new SuggestCommand(new PersonSuggestionPredicate(dayTimesInWeek, keywords)); + + } + +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/SocialNotFoundException.java b/src/main/java/seedu/address/logic/parser/exceptions/SocialNotFoundException.java new file mode 100644 index 00000000000..fd133078bd0 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/exceptions/SocialNotFoundException.java @@ -0,0 +1,10 @@ +package seedu.address.logic.parser.exceptions; + +/** + * Signals that the operation is unable to find the specified social media platform. + */ +public class SocialNotFoundException extends ParseException { + public SocialNotFoundException() { + super("Social media handle format is incorrect."); + } +} diff --git a/src/main/java/seedu/address/logic/parser/utils/CheckedFunction.java b/src/main/java/seedu/address/logic/parser/utils/CheckedFunction.java new file mode 100644 index 00000000000..0b31bc0ce84 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/utils/CheckedFunction.java @@ -0,0 +1,14 @@ +package seedu.address.logic.parser.utils; + +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * This is a functional interface which represents a function that can throw {@code ParseException}. + * + * @param the type of the exception thrown by the function + * @param the type of the result of the function + */ +@FunctionalInterface +public interface CheckedFunction { + R apply(T t) throws ParseException; +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java index 60472ca22a0..751513b0e61 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/person/Address.java @@ -9,13 +9,13 @@ */ public class Address { - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; + public static final String MESSAGE_CONSTRAINTS = "Any string can be accepted"; /* * The first character of the address must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ - public static final String VALIDATION_REGEX = "[^\\s].*"; + public static final String VALIDATION_REGEX = ".*"; public final String value; @@ -31,12 +31,19 @@ public Address(String address) { } /** - * Returns true if a given string is a valid email. + * Returns true if a given string is a valid address. + * + * @param test A string. + * @return A boolean value. */ public static boolean isValidAddress(String test) { return test.matches(VALIDATION_REGEX); } + /** + * Returns the String representation of the object. + * @return String + */ @Override public String toString() { return value; @@ -49,6 +56,10 @@ public boolean equals(Object other) { && value.equals(((Address) other).value)); // state check } + /** + * Returns hashcode for purpose of the {@link #equals(Object)} method. + * @return The hashcode of the String representation of the object. + */ @Override public int hashCode() { return value.hashCode(); diff --git a/src/main/java/seedu/address/model/person/Country.java b/src/main/java/seedu/address/model/person/Country.java new file mode 100644 index 00000000000..b5508f43c40 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Country.java @@ -0,0 +1,53 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's country in the address book. + */ +public class Country { + + public static final String MESSAGE_CONSTRAINTS = "Country should only contain alphabets."; + + public static final String VALIDATION_REGEX = "[A-Za-z ]*?"; + + public final String country; + + /** + * Constructs a {@code Country}. + * @param fullCountry + */ + public Country(String fullCountry) { + requireNonNull(fullCountry); + checkArgument(isValidCountry(fullCountry), MESSAGE_CONSTRAINTS); + country = fullCountry; + } + + /** + * Returns true if a given string is a valid country. + * + * @param test A string. + * @return A boolean value. + */ + public static boolean isValidCountry(String test) { + return test.matches(VALIDATION_REGEX); + } + + public static String getMessageConstraints() { + return MESSAGE_CONSTRAINTS; + } + + @Override + public boolean equals(Object other) { + return this == other //short circuit if same object + || (other instanceof Country + && country.equals(((Country) other).country)); + } + + @Override + public String toString() { + return country; + } + +} diff --git a/src/main/java/seedu/address/model/person/DayTimeInWeek.java b/src/main/java/seedu/address/model/person/DayTimeInWeek.java new file mode 100644 index 00000000000..0d4f3bd3650 --- /dev/null +++ b/src/main/java/seedu/address/model/person/DayTimeInWeek.java @@ -0,0 +1,196 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents the day of week and time of day together. + */ +public class DayTimeInWeek { + + public static final int MINUTES_IN_A_WEEK = 60 * 24 * 7; + public static final int MINUTES_IN_A_DAY = 60 * 24; + public static final int MINUTES_IN_AN_HOUR = 60; + + public static final String MESSAGE_CONSTRAINTS = + "Day time in week must be in the form fri@1200."; + + public static final String ILLEGAL_TIME_CONSTRAINTS = + "Time must be in valid format! For example, 12.30am should be written as 0030" + + " and there cannot be more than 60 minutes in one hour."; + + /* + * Completely no whitespace in the name + */ + public static final String VALIDATION_REGEX = "(mon|tue|wed|thu|fri|sat|sun)@([0-9]{4})"; + public static final String CURRENT_TIME_COMMAND = "now"; + + public final Integer minutesSinceMondayMidnight; + + /** + * Constructs a {@code DayTimeInWeek}. + * + * @param dayTimeInWeek A valid String representing a time in the week. + */ + public DayTimeInWeek(String dayTimeInWeek) { + requireNonNull(dayTimeInWeek); + checkArgument(isValidDayTimeInWeekRegex(dayTimeInWeek), MESSAGE_CONSTRAINTS); + checkArgument(isValidDayTimeInWeekParsing(dayTimeInWeek), ILLEGAL_TIME_CONSTRAINTS); + this.minutesSinceMondayMidnight = stringToMinutesSinceMondayMidnight(dayTimeInWeek); + } + + /** + * Returns true if the given string matches the valid regex for DayTimeInWeek. + * @param test A string. + * @return A boolean value. + */ + public static boolean isValidDayTimeInWeekRegex(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * Returns true if a given string can be converted to + * a DayTimeInWeek. + * @param test The given String. + * @return true if the String can be converted to a DayTimeInWeek. + */ + public static boolean isValidDayTimeInWeekParsing(String test) { + String[] tokens = test.split("@"); + String timeOfDay = tokens[1]; + + int minutes = Integer.parseInt(timeOfDay.substring(2, 4)); + if (minutes < 0 || minutes >= 60) { + return false; + } + + if (stringToMinutesSinceMidnight(timeOfDay) >= MINUTES_IN_A_DAY) { + return false; + } + + return stringToMinutesSinceMondayMidnight(test) < MINUTES_IN_A_WEEK; + } + + public static boolean isFindTimeNow(String test) { + return test.equals(CURRENT_TIME_COMMAND); + } + + /** + * Converts the String representation of TimeOfDay to minutes since midnight. + * @param timeOfDay A String representation of the TimeOfDay. + * @return Minutes elapsed since midnight. + */ + private static int stringToMinutesSinceMidnight(String timeOfDay) { + + String hourStr = timeOfDay.substring(0, 2); + String minuteStr = timeOfDay.substring(2, 4); + int hour = Integer.parseInt(hourStr); + int minute = Integer.parseInt(minuteStr); + return hour * 60 + minute; + + } + + /** + * Converts the String representation of DayTimeInWeek to minutes since midnight. + * @param s A String representation of the DayTimeInWeek. + * @return Minutes elapsed since Monday midnight. + */ + private static int stringToMinutesSinceMondayMidnight(String s) { + + String[] tokens = s.split("@"); + String dayOfWeek = tokens[0]; + String timeOfDay = tokens[1]; + + int dayOfWeekInt = getDayOfWeekNumeric(dayOfWeek); + String hourStr = timeOfDay.substring(0, 2); + String minuteStr = timeOfDay.substring(2, 4); + int hourInt = Integer.parseInt(hourStr); + int minuteInt = Integer.parseInt(minuteStr); + + return dayOfWeekInt * MINUTES_IN_A_DAY + hourInt * MINUTES_IN_AN_HOUR + minuteInt; + + } + + /** + * Gets the number of days passed since Monday. + * @param dayOfWeek A valid dayOfWeek + * @return Number of days passed since Monday. + */ + public static int getDayOfWeekNumeric(String dayOfWeek) { + + switch (dayOfWeek) { + case "mon": + return 0; + case "tue": + return 1; + case "wed": + return 2; + case "thu": + return 3; + case "fri": + return 4; + case "sat": + return 5; + case "sun": + return 6; + default: + assert false; + return -1; + } + + } + + /** + * Gets the day of week in String. + * @param dayOfWeek A valid dayOfWeek + * @return String representation of the day of week. + */ + public static String getDayOfWeekString(int dayOfWeek) { + + switch (dayOfWeek) { + case 0: + return "mon"; + case 1: + return "tue"; + case 2: + return "wed"; + case 3: + return "thu"; + case 4: + return "fri"; + case 5: + return "sat"; + case 6: + return "sun"; + default: + assert false; + return "???"; + } + } + + @Override + public String toString() { + int day = minutesSinceMondayMidnight / MINUTES_IN_A_DAY; + int minutesSinceMidnight = minutesSinceMondayMidnight % MINUTES_IN_A_DAY; + int hour = minutesSinceMidnight / MINUTES_IN_AN_HOUR; + int minute = minutesSinceMidnight % MINUTES_IN_AN_HOUR; + return getDayOfWeekString(day) + "@" + String.format("%02d%02d", hour, minute); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DayTimeInWeek // instanceof handles nulls + && minutesSinceMondayMidnight.equals(((DayTimeInWeek) other) + .minutesSinceMondayMidnight)); // state check + } + + /** + * Returns hashcode for purpose of the {@link #equals(Object)} method. + * @return The hashcode of the String representation of the object. + */ + @Override + public int hashCode() { + return minutesSinceMondayMidnight.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java index f866e7133de..5ae10d038b0 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/person/Email.java @@ -9,27 +9,8 @@ */ public class Email { - private static final String SPECIAL_CHARACTERS = "+_.-"; - public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain " - + "and adhere to the following constraints:\n" - + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " - + "the parentheses, (" + SPECIAL_CHARACTERS + "). The local-part may not start or end with any special " - + "characters.\n" - + "2. This is followed by a '@' and then a domain name. The domain name is made up of domain labels " - + "separated by periods.\n" - + "The domain name must:\n" - + " - end with a domain label at least 2 characters long\n" - + " - have each domain label start and end with alphanumeric characters\n" - + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any."; - // alphanumeric and special characters - private static final String ALPHANUMERIC_NO_UNDERSCORE = "[^\\W_]+"; // alphanumeric characters except underscore - private static final String LOCAL_PART_REGEX = "^" + ALPHANUMERIC_NO_UNDERSCORE + "([" + SPECIAL_CHARACTERS + "]" - + ALPHANUMERIC_NO_UNDERSCORE + ")*"; - private static final String DOMAIN_PART_REGEX = ALPHANUMERIC_NO_UNDERSCORE - + "(-" + ALPHANUMERIC_NO_UNDERSCORE + ")*"; - private static final String DOMAIN_LAST_PART_REGEX = "(" + DOMAIN_PART_REGEX + "){2,}$"; // At least two chars - private static final String DOMAIN_REGEX = "(" + DOMAIN_PART_REGEX + "\\.)*" + DOMAIN_LAST_PART_REGEX; - public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_REGEX; + public static final String VALIDATION_REGEX = ".*"; + public static final String MESSAGE_CONSTRAINTS = "Any string can be accepted"; public final String value; @@ -45,7 +26,10 @@ public Email(String email) { } /** - * Returns if a given string is a valid email. + * Returns true if a given string is a valid email. + * + * @param test A string. + * @return A boolean value. */ public static boolean isValidEmail(String test) { return test.matches(VALIDATION_REGEX); @@ -63,6 +47,10 @@ public boolean equals(Object other) { && value.equals(((Email) other).value)); // state check } + /** + * Returns hashcode for purpose of the {@link #equals(Object)} method. + * @return The hashcode of the String representation of the object. + */ @Override public int hashCode() { return value.hashCode(); diff --git a/src/main/java/seedu/address/model/person/GameType.java b/src/main/java/seedu/address/model/person/GameType.java new file mode 100644 index 00000000000..39fdf50eb15 --- /dev/null +++ b/src/main/java/seedu/address/model/person/GameType.java @@ -0,0 +1,81 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's preferred game type in the address book. + */ +public class GameType { + + public static final String MESSAGE_CONSTRAINTS = + "Any string can be accepted"; + + /* + * The first character of the social must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = ".*"; + + private String preferredGameType; + + /** + * Constructs a {@code GameType}. + * + * @param gameType a valid preferred game type. + */ + + public GameType(String gameType) { + requireNonNull(gameType); + checkArgument(isValidGameType(gameType), MESSAGE_CONSTRAINTS); + preferredGameType = gameType; + } + + /** + * Returns true if a given string is a valid game type. + * + * @param test A string. + * @return A boolean value. + */ + public static boolean isValidGameType(String test) { + return test.matches(VALIDATION_REGEX); + } + + public String toString() { + return preferredGameType; + } + + public String getGameTypeName() { + return preferredGameType; + } + + public static String getMessageConstraints() { + return MESSAGE_CONSTRAINTS; + } + + /** + * Returns hashcode for purpose of the {@link #equals(Object)} method. + * @return The hashcode of the String representation of the object. + */ + @Override + public int hashCode() { + return this.getGameTypeName().hashCode(); + } + + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } else if (!(other instanceof GameType)) { + return false; + } + + GameType gameType = (GameType) other; + + return this.preferredGameType.equals(gameType.preferredGameType); + + } + +} + diff --git a/src/main/java/seedu/address/model/person/ITimesAvailable.java b/src/main/java/seedu/address/model/person/ITimesAvailable.java new file mode 100644 index 00000000000..f6b19d876f7 --- /dev/null +++ b/src/main/java/seedu/address/model/person/ITimesAvailable.java @@ -0,0 +1,11 @@ +package seedu.address.model.person; + +/** + * Represents the timings in a week in which a friend + * is available to play Minecraft. + */ +public interface ITimesAvailable { + + boolean isAvailable(DayTimeInWeek dayTimeInWeek); + +} diff --git a/src/main/java/seedu/address/model/person/Keyword.java b/src/main/java/seedu/address/model/person/Keyword.java new file mode 100644 index 00000000000..64b5d8788e2 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Keyword.java @@ -0,0 +1,63 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a keyword for use in the command "suggest". + */ +public class Keyword { + + public static final String MESSAGE_CONSTRAINTS = "Any string can be accepted"; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = ".*"; + + public final String value; + + /** + * Constructs an {@code Keyword}. + * + * @param keyword A valid keyword. + */ + public Keyword(String keyword) { + requireNonNull(keyword); + checkArgument(isValidKeyword(keyword), MESSAGE_CONSTRAINTS); + value = keyword; + } + + /** + * Returns true if a given string is a valid keyword. + * + * @param test A string. + * @return A boolean value. + */ + public static boolean isValidKeyword(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Keyword // instanceof handles nulls + && value.equals(((Keyword) other).value)); // state check + } + + /** + * Returns hashcode for purpose of the {@link #equals(Object)} method. + * @return The hashcode of the String representation of the object. + */ + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/MinecraftName.java b/src/main/java/seedu/address/model/person/MinecraftName.java new file mode 100644 index 00000000000..08db4f0656f --- /dev/null +++ b/src/main/java/seedu/address/model/person/MinecraftName.java @@ -0,0 +1,64 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's minecraft username in the address book. + */ +public class MinecraftName { + + public static final String MESSAGE_CONSTRAINTS = + "Minecraft names cannot have any spaces, and must contain at least one character."; + + /* + * Completely no whitespace in the name + */ + public static final String VALIDATION_REGEX = "^\\S+$"; + + public final String username; + + /** + * Constructs a {@code Name}. + * + * @param username A valid Minecraft username. + */ + public MinecraftName(String username) { + requireNonNull(username); + checkArgument(isValidMinecraftName(username), MESSAGE_CONSTRAINTS); + this.username = username; + } + + /** + * Returns true if a given string is a valid Minecraft username. + * + * @param test A string. + * @return A boolean value. + */ + public static boolean isValidMinecraftName(String test) { + return test.matches(VALIDATION_REGEX); + } + + + @Override + public String toString() { + return username; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MinecraftName // instanceof handles nulls + && username.equals(((MinecraftName) other).username)); // state check + } + + /** + * Returns hashcode for purpose of the {@link #equals(Object)} method. + * @return The hashcode of the String representation of the object. + */ + @Override + public int hashCode() { + return username.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java index 79244d71cf7..b980e820f16 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/person/Name.java @@ -10,13 +10,13 @@ public class Name { public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + "Any string can be accepted"; - /* + /** * The first character of the address must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ - public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + public static final String VALIDATION_REGEX = ".*"; public final String fullName; @@ -32,18 +32,27 @@ public Name(String name) { } /** - * Returns true if a given string is a valid name. + * Returns true if the given String is a valid name. + * @param test A string. + * @return A boolean value. */ public static boolean isValidName(String test) { return test.matches(VALIDATION_REGEX); } - + /** + * Returns the String representation of the object. + * @return String + */ @Override public String toString() { return fullName; } + /** + * Returns hashcode for purpose of the {@code #equals(Object)} method. + * @return The hashcode of the String representation of the object. + */ @Override public boolean equals(Object other) { return other == this // short circuit if same object diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java index c9b5868427c..8deba0ddc3b 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java @@ -11,10 +11,20 @@ public class NameContainsKeywordsPredicate implements Predicate { private final List keywords; + /** + * Constructs a {@code NameContainsKeywordsPredicate}. + * + * @param keywords A list of keywords. + */ public NameContainsKeywordsPredicate(List keywords) { this.keywords = keywords; } + /** + * Tests if the {@code Person}'s name matches any of the keywords given. + * @param person A Person. + * @return A boolean value. + */ @Override public boolean test(Person person) { return keywords.stream() diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index 8ff1d83fe89..31ccf088968 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -2,12 +2,14 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Objects; import java.util.Set; - -import seedu.address.model.tag.Tag; +import java.util.stream.Collectors; /** * Represents a Person in the address book. @@ -17,29 +19,61 @@ public class Person { // Identity fields private final Name name; + private final MinecraftName minecraftName; private final Phone phone; private final Email email; // Data fields private final Address address; - private final Set tags = new HashSet<>(); + private final Set socials; + private final Country country; + private final Set servers; + private final Set gameTypes; + private final Set tags; + private final Set timeIntervals; + /** - * Every field must be present and not null. + * Constructs a {@code Person}. + * + * @param name A valid name. + * @param minecraftName A valid minecraft name. + * @param phone A valid phone number. + * @param email A valid email address. + * @param address A valid home address. + * @param socials A valid set of social handles. + * @param tags A valid set of tags. + * @param servers A valid set of minecraft servers. + * @param country A valid country. + * @param gameTypes A valid set of minecraft game types. + * @param timeIntervals A valid set of time intervals. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(Name name, MinecraftName minecraftName, Phone phone, Email email, Address address, + Set socials, Set tags, Set servers, Country country, Set gameTypes, + Set timeIntervals) { + requireAllNonNull(name, minecraftName); + this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); + this.minecraftName = minecraftName; + this.phone = phone == null ? new Phone("") : phone; + this.email = email == null ? new Email("") : email; + this.address = address == null ? new Address("") : address; + this.socials = socials == null ? new HashSet<>() : socials; + this.tags = tags == null ? new HashSet<>() : tags; + this.servers = servers == null ? new HashSet<>() : servers; + this.country = country == null ? new Country("") : country; + this.gameTypes = gameTypes == null ? new HashSet<>() : gameTypes; + this.timeIntervals = timeIntervals == null ? new HashSet<>() : timeIntervals; } public Name getName() { return name; } + public MinecraftName getMinecraftName() { + return minecraftName; + } + public Phone getPhone() { return phone; } @@ -52,30 +86,79 @@ public Address getAddress() { return address; } + public Country getCountry() { + return country; + } + + /** + * Returns an immutable social set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * @return A Set collection that contains {@code Social} + */ + public Set getSocials() { + return Collections.unmodifiableSet(socials); + } + /** * Returns an immutable tag set, which throws {@code UnsupportedOperationException} * if modification is attempted. + * @return A Set collection that contains {@code Tag} */ public Set getTags() { return Collections.unmodifiableSet(tags); } /** - * Returns true if both persons have the same name. - * This defines a weaker notion of equality between two persons. + * Returns an immutable server set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * @return A Set collection that contains {@code Server} + */ + public Set getServers() { + return Collections.unmodifiableSet(servers); + } + + /** + * Returns an immutable game type set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * @return A Set collection that contains {@code GameType} + */ + public Set getGameType() { + return Collections.unmodifiableSet(gameTypes); + } + + /** + * Returns an immutable time interval set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * @return A Set collection that contains {@code ITimesAvailable} + */ + public Set getTimesAvailable() { + return Collections.unmodifiableSet(timeIntervals); + } + + /** + * Returns true if both persons have the same Minecraft name. + * Minecraft names are guaranteed to be unique, + * so this defines a weaker notion of equality between two persons. + * @param otherPerson A second {@code Person}. + * @return A boolean value. */ public boolean isSamePerson(Person otherPerson) { if (otherPerson == this) { return true; } - return otherPerson != null - && otherPerson.getName().equals(getName()); + if (otherPerson == null) { + return false; + } + + return otherPerson.getMinecraftName().equals(getMinecraftName()); } /** * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. + * This defines a stronger notion of quality between two persons. + * @param other The other object. + * @return A boolean value. */ @Override public boolean equals(Object other) { @@ -89,12 +172,13 @@ public boolean equals(Object other) { Person otherPerson = (Person) other; return otherPerson.getName().equals(getName()) - && otherPerson.getPhone().equals(getPhone()) - && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); + && otherPerson.getMinecraftName().equals(getMinecraftName()); } + /** + * Returns hashcode for purpose of the {@link #equals(Object)} method. + * @return The hashcode. + */ @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own @@ -103,21 +187,64 @@ public int hashCode() { @Override public String toString() { + final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append("; Phone: ") - .append(getPhone()) - .append("; Email: ") - .append(getEmail()) - .append("; Address: ") - .append(getAddress()); - - Set tags = getTags(); - if (!tags.isEmpty()) { - builder.append("; Tags: "); - tags.forEach(builder::append); - } + builder.append(getName()).append("; Minecraft Username: ").append(getMinecraftName()); return builder.toString(); + + } + + /** + * Gets the string to be displayed in the main UI page. + * @return The display string + */ + public String toDisplayString() { + + final StringBuilder builder = new StringBuilder(); + builder.append("Phone: ").append(getFieldOrElse(getPhone().toString())).append("\n"); + builder.append("Email: ").append(getFieldOrElse(getEmail().toString())).append("\n"); + builder.append("Address: ").append(getFieldOrElse(getAddress().toString())).append("\n"); + builder.append("Country: ").append(getFieldOrElse(getCountry().toString())).append("\n"); + builder.append("Available timings: ").append(getFieldOrElse(getTimesAvailable())).append("\n"); + + return builder.toString(); + } + + /** + * Gets the strings to be checked against in the predicate. + * @return The collection of strings to be checked against + */ + public Collection toPredicateCheckingString() { + + List predStrings = new ArrayList<>(); + predStrings.add(getName().toString()); + predStrings.add(getMinecraftName().toString()); + predStrings.add(getPhone().toString()); + predStrings.add(getAddress().toString()); + predStrings.add(getCountry().toString()); + predStrings.addAll(getCollectionString(getServers())); + predStrings.addAll(getCollectionString(getSocials())); + predStrings.addAll(getCollectionString(getGameType())); + predStrings.addAll(getCollectionString(getTags())); + + return predStrings; + } + + /** + * Converts a collection of attributes to a collection of Strings. + * @param c The collection of the attributes of a person + * @return The collection of Strings representing that attribute + */ + private Collection getCollectionString(Collection c) { + return c.stream().map(Object::toString).collect(Collectors.toList()); + } + + private String getFieldOrElse(String s) { + return s.isEmpty() ? "N/A" : s; + } + + private String getFieldOrElse(Collection c) { + return c.isEmpty() ? "None available" : c.toString(); } } diff --git a/src/main/java/seedu/address/model/person/PersonSuggestionPredicate.java b/src/main/java/seedu/address/model/person/PersonSuggestionPredicate.java new file mode 100644 index 00000000000..1814c2a3958 --- /dev/null +++ b/src/main/java/seedu/address/model/person/PersonSuggestionPredicate.java @@ -0,0 +1,70 @@ +package seedu.address.model.person; + +import java.util.Collection; +import java.util.Set; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Represents a predicate to check if a Minecraft friend should be suggested + * based on some supplied attribute constraints. + */ +public class PersonSuggestionPredicate implements Predicate { + + private final Set dayTimeInWeek; + private final Set keywords; + + /** + * Creates a predicate based off the constraints below. + * @param dayTimeInWeek The Set collection that contains {@code DayTimeInWeek}. + * @param keywords The Set collection that contains the {@code Keyword} that the friend needs to contain. + */ + public PersonSuggestionPredicate(Set dayTimeInWeek, Set keywords) { + this.dayTimeInWeek = dayTimeInWeek; + this.keywords = keywords; + } + + /** + * Tests if the {@code person} should be suggested based on the attribute constraints given. + * @param person A {@code Person}. + * @return A boolean value. + */ + @Override + public boolean test(Person person) { + + Collection predStrings = person.toPredicateCheckingString(); + + // All keywords must be matched + boolean isKeywordMatching = keywords.stream() + .allMatch(keyword -> predStrings.stream() + .anyMatch(ps -> StringUtil.containsIgnoreCase(ps, keyword.toString()))); + + // Only one time interval need to be matched + boolean isDayTimeInWeekMatching = true; + if (!dayTimeInWeek.isEmpty()) { + if (!dayTimeInWeek.stream() + .anyMatch(dayTimeInWeek -> person.getTimesAvailable().stream() + .anyMatch(iTimesAvailable -> iTimesAvailable.isAvailable(dayTimeInWeek)))) { + isDayTimeInWeekMatching = false; + } + } + + // Both the keyword and time interval conditions must be satisfied + return isDayTimeInWeekMatching && isKeywordMatching; + } + + @Override + public boolean equals(Object other) { + + if (other == this) { + return true; + } else if (!(other instanceof PersonSuggestionPredicate)) { + return false; + } + + PersonSuggestionPredicate pred = (PersonSuggestionPredicate) other; + return pred.keywords.equals(this.keywords) && pred.dayTimeInWeek.equals(this.dayTimeInWeek); + + } +} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java index 872c76b382f..d98347f26bc 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/person/Phone.java @@ -10,9 +10,9 @@ public class Phone { - public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; - public static final String VALIDATION_REGEX = "\\d{3,}"; + public static final String MESSAGE_CONSTRAINTS = "Phone numbers should only contain numbers with " + + " or without a preceding +."; + public static final String VALIDATION_REGEX = "^\\+?[0-9]*$"; public final String value; /** @@ -28,6 +28,8 @@ public Phone(String phone) { /** * Returns true if a given string is a valid phone number. + * @param test A string. + * @return A boolean value. */ public static boolean isValidPhone(String test) { return test.matches(VALIDATION_REGEX); @@ -45,6 +47,10 @@ public boolean equals(Object other) { && value.equals(((Phone) other).value)); // state check } + /** + * Returns hashcode for purpose of the {@link #equals(Object)} method. + * @return The hashcode of the String representation of the object. + */ @Override public int hashCode() { return value.hashCode(); diff --git a/src/main/java/seedu/address/model/person/Server.java b/src/main/java/seedu/address/model/person/Server.java new file mode 100644 index 00000000000..7c361ff1b23 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Server.java @@ -0,0 +1,67 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's server in the address book. + */ +public class Server { + + private static final String VALIDATION_REGEX = + "^.+@.+"; + + private static final String SERVER_CONSTRAINTS = + "Minecraft server input follows the format server_name@server_address."; + + private final String serverName; + + /** + * Constructs a {@code Server}. + * @param serverName the server's name + */ + public Server(String serverName) { + requireNonNull(serverName); + checkArgument(isValidServerName(serverName), SERVER_CONSTRAINTS); + this.serverName = serverName; + } + + /** + * Returns true if a given string is a valid server name. + * + * @param test A string. + * @return A boolean value. + */ + public static boolean isValidServerName(String test) { + return test.matches(VALIDATION_REGEX); + } + + public String getServerName() { + return serverName; + } + + public static String getServerConstraints() { + return SERVER_CONSTRAINTS; + } + + /** + * Returns hashcode for purpose of the {@link #equals(Object)} method. + * @return The hashcode of the String representation of the object. + */ + @Override + public int hashCode() { + return this.getServerName().hashCode(); + } + + @Override + public boolean equals(Object other) { + return this == other //short circuit if the same object + || (other instanceof Server //instanceof handles null + && serverName.equals(((Server) other).getServerName())); //state check + } + + @Override + public String toString() { + return serverName; + } +} diff --git a/src/main/java/seedu/address/model/person/Social.java b/src/main/java/seedu/address/model/person/Social.java new file mode 100644 index 00000000000..0007e2f3b36 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Social.java @@ -0,0 +1,100 @@ +package seedu.address.model.person; + + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's social media in the address book. + */ +public class Social { + + public static final String MESSAGE_CONSTRAINTS = + "Social media handles should be social_media@username."; + + /** + * The first character of the social must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = ".*@.*"; + private final String platform; + private final String handle; + + + /** + * Constructs a {@code Social}. + * + * @param fullString A valid social media string, in the form of platform@handle + */ + + public Social(String fullString) { + requireNonNull(fullString); + checkArgument(isValidSocial(fullString), MESSAGE_CONSTRAINTS); + String[] fullStringArray = fullString.split("@"); + this.platform = fullStringArray[0]; + this.handle = fullStringArray[1]; + } + + /** + * Constructs a {@code Social}. + * + * @param handle A valid social media handle + * @param platform A valid social media platform + */ + + public Social(String handle, String platform) { + requireNonNull(handle); + requireNonNull(platform); + checkArgument(isValidSocial(platform + "@" + handle), MESSAGE_CONSTRAINTS); + this.platform = platform; + this.handle = handle; + } + + /** + * Returns true if a given string is a valid social handle. + * + * @param test A string. + * @return A boolean value. + */ + public static boolean isValidSocial(String test) { + String[] strArray = test.split("@"); + return strArray.length == 2 && !strArray[0].equals("") && !strArray[1].equals("") + && test.matches(VALIDATION_REGEX); + + } + + /** + * Returns hashcode for purpose of the {@link #equals(Object)} method. + * @return The hashcode of the String representation of the object. + */ + @Override + public int hashCode() { + return this.toString().hashCode(); + } + + @Override + public String toString() { + return this.platform + "@" + this.handle; + } + + /** + * Checks if string representation is the same as other's + * string representation and that other object is an instance + * of social. + * @param other + * @return True if string representation is the same as other's + * string representation and that other object is an instance + * of social. + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (!(other instanceof Social)) { + return false; + } + Social s = (Social) other; + return this.platform.equals(s.platform) && this.handle.equals(s.handle); + } + +} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/person/Tag.java similarity index 78% rename from src/main/java/seedu/address/model/tag/Tag.java rename to src/main/java/seedu/address/model/person/Tag.java index b0ea7e7dad7..e9b233aeff5 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/person/Tag.java @@ -1,4 +1,4 @@ -package seedu.address.model.tag; +package seedu.address.model.person; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; @@ -9,7 +9,7 @@ */ public class Tag { - public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; + public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric without spaces"; public static final String VALIDATION_REGEX = "\\p{Alnum}+"; public final String tagName; @@ -26,7 +26,10 @@ public Tag(String tagName) { } /** - * Returns true if a given string is a valid tag name. + * Returns true if a given string is a valid tag. + * + * @param test A string. + * @return A boolean value. */ public static boolean isValidTagName(String test) { return test.matches(VALIDATION_REGEX); @@ -39,6 +42,10 @@ public boolean equals(Object other) { && tagName.equals(((Tag) other).tagName)); // state check } + /** + * Returns hashcode for purpose of the {@link #equals(Object)} method. + * @return The hashcode of the String representation of the object. + */ @Override public int hashCode() { return tagName.hashCode(); diff --git a/src/main/java/seedu/address/model/person/TimeInterval.java b/src/main/java/seedu/address/model/person/TimeInterval.java new file mode 100644 index 00000000000..026113f0aca --- /dev/null +++ b/src/main/java/seedu/address/model/person/TimeInterval.java @@ -0,0 +1,136 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents when a Person usually goes online. + */ +public class TimeInterval implements ITimesAvailable { + + private static final String VALIDATION_REGEX = + "(mon|tue|wed|thu|fri|sat|sun)@([0-9]{4}) *- *(mon|tue|wed|thu|fri|sat|sun)@([0-9]{4})"; + private static final String TIME_INTERVAL_CONSTRAINTS = + "Time Interval should be in the form mon@2200-tue@2300"; + + private final DayTimeInWeek startTime; + private final DayTimeInWeek endTime; + + /** + * Constructs a {@code TimeInterval}. + * @param timeInterval A valid time interval in String, in the form of + * day@time-day@time. + */ + public TimeInterval(String timeInterval) { + requireNonNull(timeInterval); + checkArgument(isValidTimeInterval(timeInterval), TIME_INTERVAL_CONSTRAINTS); + String startTime = getStartingDayTimeInWeek(timeInterval); + checkArgument(DayTimeInWeek.isValidDayTimeInWeekParsing(startTime), + DayTimeInWeek.ILLEGAL_TIME_CONSTRAINTS); + String endTime = getEndingDayTimeInWeek(timeInterval); + checkArgument(DayTimeInWeek.isValidDayTimeInWeekParsing(endTime), + DayTimeInWeek.ILLEGAL_TIME_CONSTRAINTS); + this.startTime = makeStartTime(startTime); + this.endTime = makeEndTime(endTime); + } + + /** + * Returns the start time of the time interval in DayTimeInWeek. + * @param startTime the valid DayTimeInWeek in String. + * @return the start time in DayTimeInWeek. + */ + public static DayTimeInWeek makeStartTime(String startTime) { + return new DayTimeInWeek(startTime); + } + + /** + * Returns the end time of the time interval in DayTimeInWeek. + * @param endTime the valid DayTimeInWeek in String. + * @return the end time in DayTimeInWeek. + */ + public static DayTimeInWeek makeEndTime(String endTime) { + return new DayTimeInWeek(endTime); + } + + /** + * Returns true if the given String is a valid time interval. + * @param test A string. + * @return A boolean value. + */ + public static boolean isValidTimeInterval(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * Returns the String representation of the point in time at which the given time interval starts. + * @param test A time interval. + * @return A start time. + */ + public static String getStartingDayTimeInWeek(String test) { + assert isValidTimeInterval(test); + String[] tokens = test.split("-"); + return tokens[0].trim(); + } + + /** + * Returns the String representation of the point in time at which the given time interval ends. + * @param test A time interval. + * @return An end time. + */ + public static String getEndingDayTimeInWeek(String test) { + assert isValidTimeInterval(test); + String[] tokens = test.split("-"); + return tokens[1].trim(); + } + + public static String getTimeIntervalConstraints() { + return TIME_INTERVAL_CONSTRAINTS; + } + + /** + * Returns true if the given {@code dayTimeInWeek} lies within this {@code TimeInterval}. + * @param dayTimeInWeek A {@code DayTimeInWeek}. + * @return A boolean value. + */ + @Override + public boolean isAvailable(DayTimeInWeek dayTimeInWeek) { + // Must consider the case where user is available from Sunday night to Monday morning + if (startTime.minutesSinceMondayMidnight > endTime.minutesSinceMondayMidnight) { + return dayTimeInWeek.minutesSinceMondayMidnight >= startTime.minutesSinceMondayMidnight + || dayTimeInWeek.minutesSinceMondayMidnight <= endTime.minutesSinceMondayMidnight; + } else { + return dayTimeInWeek.minutesSinceMondayMidnight >= startTime.minutesSinceMondayMidnight + && dayTimeInWeek.minutesSinceMondayMidnight <= endTime.minutesSinceMondayMidnight; + } + } + + /** + * Returns hashcode for purpose of the {@link #equals(Object)} method. + * @return The hashcode of the String representation of the object. + */ + @Override + public int hashCode() { + return this.toString().hashCode(); + } + + @Override + public String toString() { + return startTime + "-" + endTime; + } + + /** + * Checks if a TimeInterval object is equal to another + * object. + * @param other Another object of comparison. + * @return true if they are of the same object + * or if they are different TimeInterval objects with the same start + * and end time. + */ + @Override + public boolean equals(Object other) { + return this == other // short circuit if same object + || (other instanceof TimeInterval // instanceof handles null + && startTime.equals(((TimeInterval) other).startTime) // state check + && endTime.equals(((TimeInterval) other).endTime)); + } +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index 0fee4fe57e6..77757edd0ad 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -79,13 +79,17 @@ public void remove(Person toRemove) { } } + /** + * Replaces the contents of this list with all the {@code persons} in the given {@link UniquePersonList}. + * {@code UniquePersonList} must not contain duplicate persons. + */ public void setPersons(UniquePersonList replacement) { requireNonNull(replacement); internalList.setAll(replacement.internalList); } /** - * Replaces the contents of this list with {@code persons}. + * Replaces the contents of this list with all the {@code Person}s inside the given {@code persons} list. * {@code persons} must not contain duplicate persons. */ public void setPersons(List persons) { diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java deleted file mode 100644 index 1806da4facf..00000000000 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.model.util; - -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Contains utility methods for populating {@code AddressBook} with sample data. - */ -public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) - }; - } - - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); - } - return sampleAb; - } - - /** - * Returns a tag set containing the list of strings given. - */ - public static Set getTagSet(String... strings) { - return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedGameType.java b/src/main/java/seedu/address/storage/JsonAdaptedGameType.java new file mode 100644 index 00000000000..52b1c1069b0 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedGameType.java @@ -0,0 +1,50 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.model.person.GameType; + + +/** + * Jackson-friendly version of {@link seedu.address.model.person.GameType}. + */ +class JsonAdaptedGameType { + + private final String gameTypes; + + /** + * Constructs a {@code JsonAdaptedGameType} with the given {@code gameTypes}. + */ + @JsonCreator + public JsonAdaptedGameType(String gameTypes) { + this.gameTypes = gameTypes; + } + + /** + * Converts a given {@code gameTypes} into this class for Jackson use. + */ + public JsonAdaptedGameType(GameType source) { + gameTypes = source.toString(); + } + + @JsonValue + public String getGameTypes() { + return gameTypes; + } + + /** + * Converts this Jackson-friendly adapted socials object into the model's {@code GameType} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted game types. + */ + public GameType toModelType() throws IllegalValueException { + if (!GameType.isValidGameType(gameTypes)) { + throw new IllegalValueException(GameType.MESSAGE_CONSTRAINTS); + } + return ParserUtil.parseGameType(gameTypes); + } +} + diff --git a/src/main/java/seedu/address/storage/JsonAdaptedMinecraftServer.java b/src/main/java/seedu/address/storage/JsonAdaptedMinecraftServer.java new file mode 100644 index 00000000000..fda6dad474d --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedMinecraftServer.java @@ -0,0 +1,41 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Server; + +/** + * Jackson-friendly version of {@link Server}. + */ +public class JsonAdaptedMinecraftServer { + + private final String serverName; + + @JsonCreator + public JsonAdaptedMinecraftServer(String serverName) { + this.serverName = serverName; + } + + public JsonAdaptedMinecraftServer(Server source) { + serverName = source.getServerName(); + } + + @JsonValue + public String getServerName() { + return serverName; + } + + /** + * Parses the serverName to produce a Server object. + * @return the server object + * @throws IllegalValueException if the serverName is invalid + */ + public Server toModelType() throws IllegalValueException { + if (!Server.isValidServerName(serverName)) { + throw new IllegalValueException(Server.getServerConstraints()); + } + return new Server(serverName); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index a6321cec2ea..6f087af34a5 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -11,11 +11,17 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.person.Address; +import seedu.address.model.person.Country; import seedu.address.model.person.Email; +import seedu.address.model.person.GameType; +import seedu.address.model.person.ITimesAvailable; +import seedu.address.model.person.MinecraftName; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Server; +import seedu.address.model.person.Social; +import seedu.address.model.person.Tag; /** * Jackson-friendly version of {@link Person}. @@ -25,38 +31,79 @@ class JsonAdaptedPerson { public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; private final String name; + private final String minecraftName; private final String phone; private final String email; private final String address; - private final List tagged = new ArrayList<>(); + private final List socials = new ArrayList<>(); + private final List tags = new ArrayList<>(); + private final List servers = new ArrayList<>(); + private final String country; + private final List gameTypes = new ArrayList<>(); + private final List timeIntervals = new ArrayList<>(); /** * Constructs a {@code JsonAdaptedPerson} with the given person details. */ @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { + public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("minecraftName") String minecraftName, + @JsonProperty("phone") String phone, @JsonProperty("email") String email, + @JsonProperty("address") String address, + @JsonProperty("socials") List socials, + @JsonProperty("tags") List tags, + @JsonProperty("servers") List servers, + @JsonProperty("country") String country, + @JsonProperty("gameTypes") List gameTypes, + @JsonProperty("timeIntervals") List timeIntervals) { this.name = name; + this.minecraftName = minecraftName; this.phone = phone; this.email = email; this.address = address; - if (tagged != null) { - this.tagged.addAll(tagged); + this.country = country; + if (socials != null) { + this.socials.addAll(socials); + } + if (tags != null) { + this.tags.addAll(tags); + } + if (servers != null) { + this.servers.addAll(servers); + } + if (gameTypes != null) { + this.gameTypes.addAll(gameTypes); + } + if (timeIntervals != null) { + this.timeIntervals.addAll(timeIntervals); } } /** - * Converts a given {@code Person} into this class for Jackson use. + * Converts a given {@code Person} into this class for json use. */ public JsonAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged.addAll(source.getTags().stream() + + name = source.getName().toString(); + minecraftName = source.getMinecraftName().toString(); + phone = source.getPhone().toString(); + email = source.getEmail().toString(); + address = source.getAddress().toString(); + country = source.getCountry().toString(); + socials.addAll(source.getSocials().stream() + .map(JsonAdaptedSocial::new) + .collect(Collectors.toList())); + tags.addAll(source.getTags().stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList())); + servers.addAll(source.getServers().stream() + .map(JsonAdaptedMinecraftServer::new) + .collect(Collectors.toList())); + gameTypes.addAll(source.getGameType().stream() + .map(JsonAdaptedGameType::new) + .collect(Collectors.toList())); + timeIntervals.addAll(source.getTimesAvailable().stream() + .map(JsonAdaptedTimeInterval::new) + .collect(Collectors.toList())); } /** @@ -65,9 +112,30 @@ public JsonAdaptedPerson(Person source) { * @throws IllegalValueException if there were any data constraints violated in the adapted person. */ public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); + + final List tags = new ArrayList<>(); + for (JsonAdaptedTag tag : this.tags) { + tags.add(tag.toModelType()); + } + + final List socials = new ArrayList<>(); + for (JsonAdaptedSocial social : this.socials) { + socials.add(social.toModelType()); + } + + final List servers = new ArrayList<>(); + for (JsonAdaptedMinecraftServer server : this.servers) { + servers.add(server.toModelType()); + } + + final List gameTypes = new ArrayList<>(); + for (JsonAdaptedGameType gameType : this.gameTypes) { + gameTypes.add(gameType.toModelType()); + } + + final List timeIntervals = new ArrayList<>(); + for (JsonAdaptedTimeInterval timeInterval : this.timeIntervals) { + timeIntervals.add(timeInterval.toModelType()); } if (name == null) { @@ -78,6 +146,15 @@ public Person toModelType() throws IllegalValueException { } final Name modelName = new Name(name); + if (minecraftName == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + MinecraftName.class.getSimpleName())); + } + if (!MinecraftName.isValidMinecraftName(minecraftName)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final MinecraftName modelMinecraftName = new MinecraftName(minecraftName); + if (phone == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); } @@ -100,10 +177,30 @@ public Person toModelType() throws IllegalValueException { if (!Address.isValidAddress(address)) { throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); } + if (country == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Country.class.getSimpleName())); + } + if (!Country.isValidCountry(country)) { + throw new IllegalValueException(Country.MESSAGE_CONSTRAINTS); + } + final Address modelAddress = new Address(address); - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + final Set modelTags = new HashSet<>(tags); + + final Set modelSocials = new HashSet<>(socials); + + final Set modelServers = new HashSet<>(servers); + + final Set modelGameTypes = new HashSet<>(gameTypes); + + final Country modelCountry = new Country(country); + + final Set modelTimeIntervals = new HashSet<>(timeIntervals); + + return new Person(modelName, modelMinecraftName, modelPhone, modelEmail, + modelAddress, modelSocials, modelTags, modelServers, modelCountry, modelGameTypes, modelTimeIntervals); } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedSocial.java b/src/main/java/seedu/address/storage/JsonAdaptedSocial.java new file mode 100644 index 00000000000..ca80f5d5823 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedSocial.java @@ -0,0 +1,50 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.model.person.Social; + + +/** + * Jackson-friendly version of {@link Social}. + */ +class JsonAdaptedSocial { + + private final String social; + + /** + * Constructs a {@code JsonAdaptedSocials} with the given {@code socials}. + */ + @JsonCreator + public JsonAdaptedSocial(String social) { + this.social = social; + } + + /** + * Converts a given {@code socials} into this class for Jackson use. + */ + public JsonAdaptedSocial(Social source) { + social = source.toString(); + } + + @JsonValue + public String getSocial() { + return social; + } + + /** + * Converts this Jackson-friendly adapted socials object into the model's {@code Socials} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted socials. + */ + public Social toModelType() throws IllegalValueException { + if (!Social.isValidSocial(social)) { + throw new IllegalValueException(Social.MESSAGE_CONSTRAINTS); + } + return ParserUtil.parseSocial(social); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java index 0df22bdb754..f6ecdda7a16 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedTag.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonValue; import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; +import seedu.address.model.person.Tag; /** * Jackson-friendly version of {@link Tag}. diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTimeInterval.java b/src/main/java/seedu/address/storage/JsonAdaptedTimeInterval.java new file mode 100644 index 00000000000..87aec9c9d10 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedTimeInterval.java @@ -0,0 +1,49 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.model.person.ITimesAvailable; +import seedu.address.model.person.TimeInterval; + +/** + * Jackson-friendly version of {@link TimeInterval}. + */ +class JsonAdaptedTimeInterval { + private final String timeInterval; + + /** + * Constructs a {@code JsonAdaptedTimeInterval} with the given {@code timeInterval}. + */ + @JsonCreator + public JsonAdaptedTimeInterval(String timeInterval) { + this.timeInterval = timeInterval; + } + + /** + * Converts a given {@code socials} into this class for Jackson use. + */ + public JsonAdaptedTimeInterval(ITimesAvailable source) { + timeInterval = source.toString(); + } + + @JsonValue + public String getTimeInterval() { + return timeInterval; + } + + /** + * Converts this Jackson-friendly adapted time intervals + * object into the model's {@code ITimesAvailable} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted time intervals. + */ + public ITimesAvailable toModelType() throws IllegalValueException { + if (!TimeInterval.isValidTimeInterval(timeInterval)) { + throw new IllegalValueException(TimeInterval.getTimeIntervalConstraints()); + } + return ParserUtil.parseTimeInterval(timeInterval); + } +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..dff8ee9aa81 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -57,4 +57,5 @@ public AddressBook toModelType() throws IllegalValueException { return addressBook; } + } diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 9e75478664b..8e2f79f8361 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -1,5 +1,11 @@ package seedu.address.ui; +import java.util.List; + +import org.controlsfx.control.textfield.AutoCompletionBinding; +import org.controlsfx.control.textfield.TextFields; + +import impl.org.controlsfx.skin.AutoCompletePopup; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TextField; @@ -8,6 +14,7 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; + /** * The UI component that is responsible for receiving user command inputs. */ @@ -17,6 +24,8 @@ public class CommandBox extends UiPart { private static final String FXML = "CommandBox.fxml"; private final CommandExecutor commandExecutor; + private final List commands = List.of("add", "clear", "delete", "edit", "exit", "find", + "help", "list", "suggest"); @FXML private TextField commandTextField; @@ -27,8 +36,15 @@ public class CommandBox extends UiPart { public CommandBox(CommandExecutor commandExecutor) { super(FXML); this.commandExecutor = commandExecutor; + // calls #setStyleToDefault() whenever there is a change to the text of the command box. commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); + + assert (commands.equals(List.of("add", "clear", "delete", "edit", "exit", "find", + "help", "list", "suggest"))); + + // autocomplete + setAutoComplete(); } /** @@ -82,4 +98,15 @@ public interface CommandExecutor { CommandResult execute(String commandText) throws CommandException, ParseException; } + /** + * Sets up the autocomplete feature for the command box. + */ + private void setAutoComplete() { + AutoCompletionBinding autoComplete = TextFields.bindAutoCompletion(commandTextField, commands); + AutoCompletePopup autoCompletePopup = autoComplete.getAutoCompletionPopup(); + autoCompletePopup.setStyle("-fx-font-size: 12pt;-fx-font-family: \"Minecraft\";" + + "-fx-control-inner-background:WHITE;-fx-selection-bar-non-focused:red;"); + autoComplete.setDelay(0); + } + } diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..091bf96887e 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -1,32 +1,61 @@ package seedu.address.ui; +import java.lang.reflect.InvocationTargetException; import java.util.logging.Logger; import javafx.fxml.FXML; +import javafx.scene.Scene; import javafx.scene.control.Button; +import javafx.scene.control.ChoiceBox; import javafx.scene.control.Label; import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; +import javafx.scene.text.Text; import javafx.stage.Stage; import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.SuggestCommand; /** * Controller for a help page */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; - public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; + public static final String USERGUIDE_URL = "https://ay2223s1-cs2103t-t10-4.github.io/tp/UserGuide.html"; + public static final String HELP_MESSAGE = "User Guide:"; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); private static final String FXML = "HelpWindow.fxml"; + @FXML + private ChoiceBox choiceBox; + @FXML private Button copyButton; @FXML private Label helpMessage; + @FXML + private Text desc; + + @FXML + private Text param; + + @FXML + private Text ex; + + @FXML + private Scene scene; + /** * Creates a new HelpWindow. * @@ -35,6 +64,7 @@ public class HelpWindow extends UiPart { public HelpWindow(Stage root) { super(FXML, root); helpMessage.setText(HELP_MESSAGE); + setChoiceBox(); } /** @@ -99,4 +129,73 @@ private void copyUrl() { url.putString(USERGUIDE_URL); clipboard.setContent(url); } + + /** + * Sets the choice box to display the list of commands. + */ + private void setChoiceBox() { + choiceBox.setStyle("-fx-font-family: 'Minecraft';"); + choiceBox.getItems().addAll("Add", "Delete", "Edit", "Find", "List", "Help", "Clear", "Suggest", "Exit"); + setTextAutoWrap(); + choiceBox.setOnAction(e -> { + String command = choiceBox.getValue(); + switch (command) { + case "Add": + setHelpLabels(AddCommand.class); + break; + case "Delete": + setHelpLabels(DeleteCommand.class); + break; + case "Edit": + setHelpLabels(EditCommand.class); + break; + case "Find": + setHelpLabels(FindCommand.class); + break; + case "List": + setHelpLabels(ListCommand.class); + break; + case "Help": + setHelpLabels(HelpCommand.class); + break; + case "Clear": + setHelpLabels(ClearCommand.class); + break; + case "Suggest": + setHelpLabels(SuggestCommand.class); + break; + case "Exit": + setHelpLabels(ExitCommand.class); + break; + default: + } + }); + choiceBox.setValue("Add"); + } + + /** + * Sets the help labels to display the command's description, parameters and examples. + * @param commandClass The class of the command. + */ + private void setHelpLabels(Class commandClass) { + try { + Command commandInstance = commandClass.getDeclaredConstructor().newInstance(); + desc.setText(commandInstance.getDescription()); + param.setText(commandInstance.getParameters()); + ex.setText(commandInstance.getExamples()); + } catch (InvocationTargetException | NoSuchMethodException + | InstantiationException | IllegalAccessException e) { + logger.warning("Unable to set help labels"); + e.printStackTrace(); + } + } + + /** + * Sets the text to auto wrap. + */ + private void setTextAutoWrap() { + desc.wrappingWidthProperty().bind(scene.widthProperty().subtract(20)); + param.wrappingWidthProperty().bind(scene.widthProperty().subtract(20)); + ex.wrappingWidthProperty().bind(scene.widthProperty().subtract(20)); + } } diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..6c4092fa030 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -2,14 +2,21 @@ import java.util.logging.Logger; +import javafx.animation.PauseTransition; +import javafx.animation.ScaleTransition; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.MenuItem; import javafx.scene.control.TextInputControl; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; +import javafx.scene.media.Media; +import javafx.scene.media.MediaPlayer; import javafx.stage.Stage; +import javafx.util.Duration; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; import seedu.address.logic.Logic; @@ -50,6 +57,15 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + @FXML + private StackPane creeperPlaceHolder; + + @FXML private StackPane mineFriendsPlaceHolder; + + private ImageView creeperImageView; + private ImageView explosionImageView; + private boolean isCreeperAnimationRunning; + /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ @@ -62,6 +78,7 @@ public MainWindow(Stage primaryStage, Logic logic) { // Configure the UI setWindowDefaultSize(logic.getGuiSettings()); + primaryStage.setMinWidth(800.0); setAccelerators(); @@ -121,6 +138,16 @@ void fillInnerParts() { CommandBox commandBox = new CommandBox(this::executeCommand); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + creeperImageView = new ImageView( + new Image(getClass().getResource("/images/creeper_mob.png").toString())); + explosionImageView = new ImageView( + new Image(getClass().getResource("/images/explosion2.png").toString())); + creeperImageView.setScaleX(1 / 1.2); + creeperImageView.setScaleY(1 / 1.2); + creeperPlaceHolder.getChildren().add(creeperImageView); + creeperPlaceHolder.getChildren().add(explosionImageView); + explosionImageView.setVisible(false); } /** @@ -190,7 +217,64 @@ private CommandResult executeCommand(String commandText) throws CommandException } catch (CommandException | ParseException e) { logger.info("Invalid command: " + commandText); resultDisplay.setFeedbackToUser(e.getMessage()); + executeAnimation(); throw e; } } + + /** + * Executes the animation when an invalid command is entered. + */ + private void executeAnimation() { + playSound(); + if (isCreeperAnimationRunning) { + return; + } + // start of creeper animation + isCreeperAnimationRunning = true; + ScaleTransition scaleTransitionExpand = new ScaleTransition(); + scaleTransitionExpand.setDuration(Duration.seconds(0.5)); + scaleTransitionExpand.setToX(1.2); + scaleTransitionExpand.setToY(1.2); + scaleTransitionExpand.setNode(creeperImageView); + scaleTransitionExpand.play(); + PauseTransition creeperExplosionPause = new PauseTransition(Duration.seconds(0.4375)); + creeperExplosionPause.setOnFinished( + event -> { + creeperImageView.setVisible(false); + explosionImageView.setVisible(true); + } + ); + creeperExplosionPause.play(); + PauseTransition endOfExplosionPause = new PauseTransition(Duration.seconds(0.75)); + endOfExplosionPause.setOnFinished( + event -> { + explosionImageView.setVisible(false); + creeperImageView.setScaleX(1 / 1.2); + creeperImageView.setScaleY(1 / 1.2); + creeperImageView.setVisible(true); + } + ); + endOfExplosionPause.play(); + // end of creeper animation + isCreeperAnimationRunning = false; + } + + /** + * Creates a {@code MediaPlayer} that plays a sound using the {@code soundFile} + * @param soundFile name of the sound file. + * @return A MediaPlayer object. + */ + private MediaPlayer loadSound(String soundFile) { + Media sound = new Media(getClass().getResource(soundFile).toString()); + return new MediaPlayer(sound); + } + + /** + * Plays the sound of an explosion. + */ + private void playSound() { + MediaPlayer explosionSound = loadSound("/audio/explosion_sound.mp3"); + explosionSound.play(); + } } diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 7fc927bc5d9..1a01f2526fb 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -29,32 +29,44 @@ public class PersonCard extends UiPart { @FXML private HBox cardPane; @FXML - private Label name; + private Label names; @FXML private Label id; @FXML - private Label phone; + private Label information; @FXML - private Label address; + private FlowPane tags; @FXML - private Label email; + private FlowPane socials; @FXML - private FlowPane tags; + private FlowPane servers; + @FXML + private FlowPane gameTypes; /** * Creates a {@code PersonCode} with the given {@code Person} and index to display. */ public PersonCard(Person person, int displayedIndex) { + super(FXML); this.person = person; + id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); + names.setText(person.getMinecraftName().toString() + " [" + person.getName().toString() + "]"); + information.setText(person.toDisplayString()); + person.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + + person.getSocials().stream() + .forEach(soc -> socials.getChildren().add(new Label(soc.toString()))); + + person.getServers().stream() + .forEach(ser -> servers.getChildren().add(new Label(ser.toString()))); + + person.getGameType().stream() + .forEach(gam -> gameTypes.getChildren().add(new Label(gam.toString()))); } @Override diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index fdf024138bc..236fe8010b7 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -44,6 +44,8 @@ public void start(Stage primaryStage) { mainWindow.show(); //This should be called before creating other UI parts mainWindow.fillInnerParts(); + // Shows help window at the start of the application if new user or data/addressbook.json does not exist. + showHelpWindow(); } catch (Throwable e) { logger.severe(StringUtil.getDetails(e)); showFatalErrorDialogAndShutdown("Fatal error during initializing", e); @@ -85,4 +87,12 @@ private void showFatalErrorDialogAndShutdown(String title, Throwable e) { System.exit(1); } + /** + * Checks if data/addressbook.json exists, shows help window if it does not. + */ + private void showHelpWindow() { + if (MainApp.isNewDataFileCreated()) { + mainWindow.handleHelp(); + } + } } diff --git a/src/main/resources/audio/explosion_sound.mp3 b/src/main/resources/audio/explosion_sound.mp3 new file mode 100644 index 00000000000..9257b2c998b Binary files /dev/null and b/src/main/resources/audio/explosion_sound.mp3 differ diff --git a/src/main/resources/fonts/fontstyle.css b/src/main/resources/fonts/fontstyle.css new file mode 100644 index 00000000000..20879036c92 --- /dev/null +++ b/src/main/resources/fonts/fontstyle.css @@ -0,0 +1,3 @@ +@font-face { + src: url('minecraft_font.ttf'); +} diff --git a/src/main/resources/fonts/minecraft_font.ttf b/src/main/resources/fonts/minecraft_font.ttf new file mode 100644 index 00000000000..61b4610bd20 Binary files /dev/null and b/src/main/resources/fonts/minecraft_font.ttf differ diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png index 29810cf1fd9..b119ba6723c 100644 Binary files a/src/main/resources/images/address_book_32.png and b/src/main/resources/images/address_book_32.png differ diff --git a/src/main/resources/images/creeper_mob.png b/src/main/resources/images/creeper_mob.png new file mode 100644 index 00000000000..1787796c888 Binary files /dev/null and b/src/main/resources/images/creeper_mob.png differ diff --git a/src/main/resources/images/explosion2.png b/src/main/resources/images/explosion2.png new file mode 100644 index 00000000000..760a0db0997 Binary files /dev/null and b/src/main/resources/images/explosion2.png differ diff --git a/src/main/resources/images/minefriends.png b/src/main/resources/images/minefriends.png new file mode 100644 index 00000000000..1ea96878aae Binary files /dev/null and b/src/main/resources/images/minefriends.png differ diff --git a/src/main/resources/images/texture_bedrock.png b/src/main/resources/images/texture_bedrock.png new file mode 100644 index 00000000000..6806b127aae Binary files /dev/null and b/src/main/resources/images/texture_bedrock.png differ diff --git a/src/main/resources/images/texture_dirt.png b/src/main/resources/images/texture_dirt.png new file mode 100644 index 00000000000..d804799e922 Binary files /dev/null and b/src/main/resources/images/texture_dirt.png differ diff --git a/src/main/resources/images/texture_grass.png b/src/main/resources/images/texture_grass.png new file mode 100644 index 00000000000..2bbaea286ab Binary files /dev/null and b/src/main/resources/images/texture_grass.png differ diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 09f6d6fe9e4..abbe40cb51e 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -3,7 +3,6 @@ - - + + - diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..0898ba0b491 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -1,32 +1,31 @@ .background { - -fx-background-color: derive(#1d1d1d, 20%); - background-color: #383838; /* Used in the default.html file */ + -fx-background-image: url("../images/texture_dirt.png"); } .label { -fx-font-size: 11pt; - -fx-font-family: "Segoe UI Semibold"; + -fx-font-family: 'Minecraft'; -fx-text-fill: #555555; -fx-opacity: 0.9; } .label-bright { -fx-font-size: 11pt; - -fx-font-family: "Segoe UI Semibold"; + -fx-font-family: "Minecraft"; -fx-text-fill: white; -fx-opacity: 1; } .label-header { -fx-font-size: 32pt; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Minecraft"; -fx-text-fill: white; -fx-opacity: 1; } .text-field { -fx-font-size: 12pt; - -fx-font-family: "Segoe UI Semibold"; + -fx-font-family: "Minecraft"; } .tab-pane { @@ -66,7 +65,7 @@ .table-view .column-header .label { -fx-font-size: 20pt; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Minecraft"; -fx-text-fill: white; -fx-alignment: center-left; -fx-opacity: 1; @@ -90,30 +89,24 @@ .list-view { -fx-background-insets: 0; -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-image: url("../images/texture_dirt.png"); } .list-cell { -fx-label-padding: 0 0 0 0; -fx-graphic-text-gap : 0; -fx-padding: 0 0 0 0; + -fx-border-width: 2px; } -.list-cell:filled:even { - -fx-background-color: #3c3e3f; -} - -.list-cell:filled:odd { - -fx-background-color: #515658; +.list-cell:filled { + -fx-background-image: url("../images/texture_dirt.png"); + -fx-border-color: transparent; } .list-cell:filled:selected { - -fx-background-color: #424d5f; -} - -.list-cell:filled:selected #cardPane { - -fx-border-color: #3e7b91; - -fx-border-width: 1; + -fx-background-image: url("../images/texture_dirt.png"); + -fx-border-color: white; } .list-cell .label { @@ -121,35 +114,37 @@ } .cell_big_label { - -fx-font-family: "Segoe UI Semibold"; + -fx-font-family: "Minecraft"; -fx-font-size: 16px; -fx-text-fill: #010504; } .cell_small_label { - -fx-font-family: "Segoe UI"; + -fx-font-family: "Minecraft"; -fx-font-size: 13px; -fx-text-fill: #010504; } .stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: black; + -fx-border-color: #a0a0a0; + -fx-border-width: 3px; } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-image: url("../images/texture_dirt.png"); -fx-border-color: derive(#1d1d1d, 10%); -fx-border-top-width: 1px; } .status-bar { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-image: url("../images/texture_bedrock.png"); } .result-display { -fx-background-color: transparent; - -fx-font-family: "Segoe UI Light"; - -fx-font-size: 13pt; + -fx-font-family: "Minecraft"; + -fx-font-size: 11pt; -fx-text-fill: white; } @@ -158,7 +153,7 @@ } .status-bar .label { - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Minecraft"; -fx-text-fill: white; -fx-padding: 4px; -fx-pref-height: 30px; @@ -193,12 +188,12 @@ } .menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-image: url("../images/texture_grass.png"); } .menu-bar .label { - -fx-font-size: 14pt; - -fx-font-family: "Segoe UI Light"; + -fx-font-size: 13pt; + -fx-font-family: "Minecraft"; -fx-text-fill: white; -fx-opacity: 0.9; } @@ -218,7 +213,7 @@ -fx-border-width: 2; -fx-background-radius: 0; -fx-background-color: #1d1d1d; - -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-family: "Minecraft", Helvetica, Arial, sans-serif; -fx-font-size: 11pt; -fx-text-fill: #d8d8d8; -fx-background-insets: 0 0 0 0, 0, 1, 2; @@ -322,8 +317,8 @@ -fx-background-insets: 0; -fx-border-color: #383838 #383838 #ffffff #383838; -fx-border-insets: 0; - -fx-border-width: 1; - -fx-font-family: "Segoe UI Light"; + -fx-border-width: 0; + -fx-font-family: "Minecraft"; -fx-font-size: 13pt; -fx-text-fill: white; } @@ -333,20 +328,35 @@ } #resultDisplay .content { - -fx-background-color: transparent, #383838, transparent, #383838; + -fx-background-image: url("../images/texture_dirt.png"); -fx-background-radius: 0; + -fx-font-size: 11px; } -#tags { +.tagged { -fx-hgap: 7; -fx-vgap: 3; } -#tags .label { +.tagged .label { -fx-text-fill: white; -fx-background-color: #3e7b91; -fx-padding: 1 3 1 3; - -fx-border-radius: 2; - -fx-background-radius: 2; -fx-font-size: 11; } + +#tags .label { + -fx-background-color: #3e7b91; +} + +#socials .label { + -fx-background-color: #b40ac7; +} + +#servers .label { + -fx-background-color: #11ad24; +} + +#gameTypes .label { + -fx-background-color: #ad8911; +} diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css index bfe82a85964..a265ea9d104 100644 --- a/src/main/resources/view/Extensions.css +++ b/src/main/resources/view/Extensions.css @@ -5,7 +5,8 @@ .list-cell:empty { /* Empty cells will not have alternating colours */ - -fx-background: #383838; + -fx-border-width: 0px; + -fx-background-color: transparent; } .tag-selector { @@ -18,3 +19,27 @@ .tooltip-text { -fx-text-fill: white; } + +#information { + -fx-text-fill: #B0B0B0; +} + +#resultDisplay { + -fx-background-image: url("../images/texture_dirt.png"); +} + +#personListPanelPlaceholder { + -fx-background-image: url("../images/texture_dirt.png"); +} + +#explosionPlaceHolder { + -fx-background-image: url("../images/explosion2.png"); +} + +#mineFriendsPlaceHolder { + -fx-background-image: url("../images/minefriends.png"); +} + + + + diff --git a/src/main/resources/view/HelpWindow.css b/src/main/resources/view/HelpWindow.css index 17e8a8722cd..d4214a0627a 100644 --- a/src/main/resources/view/HelpWindow.css +++ b/src/main/resources/view/HelpWindow.css @@ -1,5 +1,18 @@ -#copyButton, #helpMessage { +#title { -fx-text-fill: white; + -fx-font-family: 'Minecraft'; + -fx-font-size: 24pt; +} + + +#copyButton, #helpMessage, #description, #parameters, #examples, #choose { + -fx-text-fill: white; + -fx-font-family: 'Minecraft'; +} + +#desc, #param, #ex { + -fx-text-fill: #cccccc; + -fx-font-family: 'Minecraft'; } #copyButton { diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index 5dea0adef70..275f2c67864 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -3,42 +3,81 @@ - + + + - + - + + - + - - - - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +